From 39844be808b737a6fb339b796ec70157dbd51c1a Mon Sep 17 00:00:00 2001 From: Colin Goodheart-Smithe Date: Fri, 1 Jun 2018 19:39:47 +0100 Subject: [PATCH] Adds an explain API endpoint (#31005) * Adds an explain API endpoint This endpoint can be used to explain the current lifecycle state of an index x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifec ycle/action/ExplainLifecycleAction.java x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifec ycle/action/IndexExplainResponse.java x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/inde xlifecycle/action/TransportExplainLifecycleAction.java x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/20_m ove_to_step.yml x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClien tPlugin.java x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifec ycle/action/ExplainLifecycleAction.java x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifec ycle/action/IndexExplainResponse.java x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifec ycle/RandomStepInfo.java x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifec ycle/action/ExplainLifecycleRequestTests.java x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifec ycle/action/ExplainLifecycleResponseTests.java x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifec ycle/action/IndexExplainResponseTests.java x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/inde xlifecycle/IndexLifecycle.java x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/inde xlifecycle/action/RestExplainLifecycleAction.java x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/inde xlifecycle/action/TransportExplainLifecycleAction.java x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/inde xlifecycle/ExecuteStepsUpdateTaskTests.java x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/inde xlifecycle/IndexLifecycleRunnerTests.java x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle .explain_lifecycle.json x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/20_m ove_to_step.yml x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/30_e xplain_lifecycle.yml * Adds tests for explain API * Addresses Review comments and fixes REST tests * Removes RequestBuilder from ExplainLifecycleAction --- .../xpack/core/XPackClientPlugin.java | 17 +- .../action/ExplainLifecycleAction.java | 137 ++++++++ .../action/IndexLifecycleExplainResponse.java | 312 ++++++++++++++++++ .../core/indexlifecycle/RandomStepInfo.java | 56 ++++ .../action/ExplainLifecycleRequestTests.java | 58 ++++ .../action/ExplainLifecycleResponseTests.java | 56 ++++ .../action/IndexExplainResponseTests.java | 140 ++++++++ .../xpack/indexlifecycle/IndexLifecycle.java | 5 + .../action/RestExplainLifecycleAction.java | 43 +++ .../TransportExplainLifecycleAction.java | 88 +++++ .../ExecuteStepsUpdateTaskTests.java | 3 +- .../IndexLifecycleRunnerTests.java | 48 +-- ...ack.index_lifecycle.explain_lifecycle.json | 24 ++ .../index_lifecycle/40_explain_lifecycle.yml | 214 ++++++++++++ 14 files changed, 1152 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleAction.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexLifecycleExplainResponse.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/RandomStepInfo.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleRequestTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleResponseTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexExplainResponseTests.java create mode 100644 x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestExplainLifecycleAction.java create mode 100644 x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportExplainLifecycleAction.java create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.explain_lifecycle.json create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/40_explain_lifecycle.yml 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 206dad0acb1..994d5baddd0 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 @@ -28,6 +28,13 @@ import org.elasticsearch.license.LicensesMetaData; import org.elasticsearch.license.PostStartBasicAction; import org.elasticsearch.license.PostStartTrialAction; import org.elasticsearch.license.PutLicenseAction; +import org.elasticsearch.persistent.CompletionPersistentTaskAction; +import org.elasticsearch.persistent.PersistentTaskParams; +import org.elasticsearch.persistent.PersistentTasksCustomMetaData; +import org.elasticsearch.persistent.PersistentTasksNodeService; +import org.elasticsearch.persistent.RemovePersistentTaskAction; +import org.elasticsearch.persistent.StartPersistentTaskAction; +import org.elasticsearch.persistent.UpdatePersistentTaskStatusAction; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.NetworkPlugin; import org.elasticsearch.plugins.Plugin; @@ -52,6 +59,7 @@ 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.DeleteLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.action.PutLifecycleAction; import org.elasticsearch.xpack.core.logstash.LogstashFeatureSetUsage; @@ -76,7 +84,6 @@ import org.elasticsearch.xpack.core.ml.action.GetDatafeedsAction; import org.elasticsearch.xpack.core.ml.action.GetDatafeedsStatsAction; import org.elasticsearch.xpack.core.ml.action.GetFiltersAction; import org.elasticsearch.xpack.core.ml.action.GetInfluencersAction; -import org.elasticsearch.xpack.core.ml.action.MlInfoAction; import org.elasticsearch.xpack.core.ml.action.GetJobsAction; import org.elasticsearch.xpack.core.ml.action.GetJobsStatsAction; import org.elasticsearch.xpack.core.ml.action.GetModelSnapshotsAction; @@ -84,6 +91,7 @@ import org.elasticsearch.xpack.core.ml.action.GetOverallBucketsAction; import org.elasticsearch.xpack.core.ml.action.GetRecordsAction; import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction; import org.elasticsearch.xpack.core.ml.action.KillProcessAction; +import org.elasticsearch.xpack.core.ml.action.MlInfoAction; import org.elasticsearch.xpack.core.ml.action.OpenJobAction; import org.elasticsearch.xpack.core.ml.action.PersistJobAction; import org.elasticsearch.xpack.core.ml.action.PostCalendarEventsAction; @@ -148,6 +156,8 @@ import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl. import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction; +import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction; +import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeInfoAction; import org.elasticsearch.xpack.core.watcher.WatcherFeatureSetUsage; import org.elasticsearch.xpack.core.watcher.WatcherMetaData; import org.elasticsearch.xpack.core.watcher.transport.actions.ack.AckWatchAction; @@ -158,8 +168,6 @@ import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchAction import org.elasticsearch.xpack.core.watcher.transport.actions.put.PutWatchAction; import org.elasticsearch.xpack.core.watcher.transport.actions.service.WatcherServiceAction; import org.elasticsearch.xpack.core.watcher.transport.actions.stats.WatcherStatsAction; -import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction; -import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeInfoAction; import java.util.ArrayList; import java.util.Arrays; @@ -318,7 +326,8 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl // ILM DeleteLifecycleAction.INSTANCE, GetLifecycleAction.INSTANCE, - PutLifecycleAction.INSTANCE + PutLifecycleAction.INSTANCE, + ExplainLifecycleAction.INSTANCE ); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleAction.java new file mode 100644 index 00000000000..74039dfb84b --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleAction.java @@ -0,0 +1,137 @@ +/* + * 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.support.master.info.ClusterInfoRequest; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +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 ExplainLifecycleAction + extends Action { + public static final ExplainLifecycleAction INSTANCE = new ExplainLifecycleAction(); + public static final String NAME = "indices:admin/xpack/index_lifecycle/explain"; + + protected ExplainLifecycleAction() { + super(NAME); + } + + @Override + public Response newResponse() { + return new Response(); + } + + public static class Response extends ActionResponse implements ToXContentObject { + + private List indexResponses; + + public Response() { + } + + public Response(List indexResponses) { + this.indexResponses = indexResponses; + } + + public List getIndexResponses() { + return indexResponses; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + for (IndexLifecycleExplainResponse indexResponse : indexResponses) { + builder.field(indexResponse.getIndex(), indexResponse); + } + builder.endObject(); + return builder; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + indexResponses = in.readList(IndexLifecycleExplainResponse::new); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeList(indexResponses); + } + + @Override + public int hashCode() { + return Objects.hash(indexResponses); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj.getClass() != getClass()) { + return false; + } + Response other = (Response) obj; + return Objects.equals(indexResponses, other.indexResponses); + } + + @Override + public String toString() { + return Strings.toString(this, true, true); + } + + } + + public static class Request extends ClusterInfoRequest { + + public Request() { + super(); + } + + public Request(StreamInput in) throws IOException { + super(in); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(indices()), indicesOptions()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj.getClass() != getClass()) { + return false; + } + Request other = (Request) obj; + return Objects.deepEquals(indices(), other.indices()) && + Objects.equals(indicesOptions(), other.indicesOptions()); + } + + @Override + public String toString() { + return "Request [indices()=" + Arrays.toString(indices()) + ", indicesOptions()=" + indicesOptions() + "]"; + } + + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexLifecycleExplainResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexLifecycleExplainResponse.java new file mode 100644 index 00000000000..369996f0372 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexLifecycleExplainResponse.java @@ -0,0 +1,312 @@ +/* + * 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.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.joda.time.DateTime; +import org.joda.time.chrono.ISOChronology; + +import java.io.IOException; +import java.util.Objects; + +public class IndexLifecycleExplainResponse implements ToXContentObject, Writeable { + + private static final ParseField INDEX_FIELD = new ParseField("index"); + private static final ParseField MANAGED_BY_ILM_FIELD = new ParseField("managed"); + private static final ParseField POLICY_NAME_FIELD = new ParseField("policy"); + private static final ParseField SKIP_FIELD = new ParseField("skip"); + private static final ParseField LIFECYCLE_DATE_FIELD = new ParseField("lifecycle_date"); + private static final ParseField PHASE_FIELD = new ParseField("phase"); + private static final ParseField ACTION_FIELD = new ParseField("action"); + private static final ParseField STEP_FIELD = new ParseField("step"); + private static final ParseField FAILED_STEP_FIELD = new ParseField("failed_step"); + private static final ParseField PHASE_TIME_FIELD = new ParseField("phase_time"); + private static final ParseField ACTION_TIME_FIELD = new ParseField("action_time"); + private static final ParseField STEP_TIME_FIELD = new ParseField("step_time"); + private static final ParseField STEP_INFO_FIELD = new ParseField("step_info"); + + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "index_lifecycle_explain_response", + a -> new IndexLifecycleExplainResponse( + (String) a[0], + (boolean) a[1], + (String) a[2], + (boolean) (a[3] == null ? false: a[3]), + (long) (a[4] == null ? -1L: a[4]), + (String) a[5], + (String) a[6], + (String) a[7], + (String) a[8], + (long) (a[9] == null ? -1L: a[9]), + (long) (a[10] == null ? -1L: a[10]), + (long) (a[11] == null ? -1L: a[11]), + (BytesReference) a[12])); + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), INDEX_FIELD); + PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), MANAGED_BY_ILM_FIELD); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), POLICY_NAME_FIELD); + PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), SKIP_FIELD); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), LIFECYCLE_DATE_FIELD); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), PHASE_FIELD); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), ACTION_FIELD); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), STEP_FIELD); + PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), FAILED_STEP_FIELD); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), PHASE_TIME_FIELD); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), ACTION_TIME_FIELD); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), STEP_TIME_FIELD); + PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> { + XContentBuilder builder = JsonXContent.contentBuilder(); + builder.copyCurrentStructure(p); + return BytesArray.bytes(builder); + }, STEP_INFO_FIELD); + } + + private final String index; + private final String policyName; + private final String phase; + private final String action; + private final String step; + private final String failedStep; + private final long lifecycleDate; + private final long phaseTime; + private final long actionTime; + private final long stepTime; + private final boolean skip; + private final boolean managedByILM; + private final BytesReference stepInfo; + + public static IndexLifecycleExplainResponse newManagedIndexResponse(String index, String policyName, boolean skip, long lifecycleDate, + String phase, String action, String step, String failedStep, long phaseTime, long actionTime, long stepTime, + BytesReference stepInfo) { + return new IndexLifecycleExplainResponse(index, true, policyName, skip, lifecycleDate, phase, action, step, failedStep, phaseTime, + actionTime, stepTime, stepInfo); + } + + public static IndexLifecycleExplainResponse newUnmanagedIndexResponse(String index) { + return new IndexLifecycleExplainResponse(index, false, null, false, -1L, null, null, null, null, -1L, -1L, -1L, null); + } + + private IndexLifecycleExplainResponse(String index, boolean managedByILM, String policyName, boolean skip, long lifecycleDate, + String phase, String action, String step, String failedStep, long phaseTime, long actionTime, long stepTime, + BytesReference stepInfo) { + if (managedByILM) { + if (policyName == null) { + throw new IllegalArgumentException("[" + POLICY_NAME_FIELD.getPreferredName() + "] cannot be null for managed index"); + } + } else { + if (policyName != null || lifecycleDate >= 0 || phase != null || action != null || step != null || failedStep != null + || phaseTime >= 0 || actionTime >= 0 || stepTime >= 0 || stepInfo != null) { + throw new IllegalArgumentException( + "Unmanaged index response must only contain fields: [" + MANAGED_BY_ILM_FIELD + ", " + INDEX_FIELD + "]"); + } + } + this.index = index; + this.policyName = policyName; + this.managedByILM = managedByILM; + this.skip = skip; + this.lifecycleDate = lifecycleDate; + this.phase = phase; + this.action = action; + this.step = step; + this.phaseTime = phaseTime; + this.actionTime = actionTime; + this.stepTime = stepTime; + this.failedStep = failedStep; + this.stepInfo = stepInfo; + } + + public IndexLifecycleExplainResponse(StreamInput in) throws IOException { + index = in.readString(); + managedByILM = in.readBoolean(); + if (managedByILM) { + policyName = in.readString(); + skip = in.readBoolean(); + lifecycleDate = in.readVLong(); + phase = in.readString(); + action = in.readString(); + step = in.readString(); + failedStep = in.readOptionalString(); + phaseTime = in.readVLong(); + actionTime = in.readVLong(); + stepTime = in.readVLong(); + stepInfo = in.readOptionalBytesReference(); + + } else { + policyName = null; + skip = false; + lifecycleDate = -1L; + phase = null; + action = null; + step = null; + failedStep = null; + phaseTime = -1L; + actionTime = -1L; + stepTime = -1L; + stepInfo = null; + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(index); + out.writeBoolean(managedByILM); + if (managedByILM) { + out.writeString(policyName); + out.writeBoolean(skip); + out.writeVLong(lifecycleDate); + out.writeString(phase); + out.writeString(action); + out.writeString(step); + out.writeOptionalString(failedStep); + out.writeVLong(phaseTime); + out.writeVLong(actionTime); + out.writeVLong(stepTime); + out.writeOptionalBytesReference(stepInfo); + } + } + + public String getIndex() { + return index; + } + + public boolean managedByILM() { + return managedByILM; + } + + public String getPolicyName() { + return policyName; + } + + public boolean skip() { + return skip; + } + + public long getLifecycleDate() { + return lifecycleDate; + } + + public String getPhase() { + return phase; + } + + public long getPhaseTime() { + return phaseTime; + } + + public String getAction() { + return action; + } + + public long getActionTime() { + return actionTime; + } + + public String getStep() { + return step; + } + + public long getStepTime() { + return stepTime; + } + + public String getFailedStep() { + return failedStep; + } + + public BytesReference getStepInfo() { + return stepInfo; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(INDEX_FIELD.getPreferredName(), index); + builder.field(MANAGED_BY_ILM_FIELD.getPreferredName(), managedByILM); + if (managedByILM) { + builder.field(POLICY_NAME_FIELD.getPreferredName(), policyName); + builder.field(SKIP_FIELD.getPreferredName(), skip); + if (builder.humanReadable()) { + builder.field(LIFECYCLE_DATE_FIELD.getPreferredName(), new DateTime(lifecycleDate, ISOChronology.getInstanceUTC())); + } else { + builder.field(LIFECYCLE_DATE_FIELD.getPreferredName(), lifecycleDate); + } + builder.field(PHASE_FIELD.getPreferredName(), phase); + if (builder.humanReadable()) { + builder.field(PHASE_TIME_FIELD.getPreferredName(), new DateTime(phaseTime, ISOChronology.getInstanceUTC())); + } else { + builder.field(PHASE_TIME_FIELD.getPreferredName(), phaseTime); + } + builder.field(ACTION_FIELD.getPreferredName(), action); + if (builder.humanReadable()) { + builder.field(ACTION_TIME_FIELD.getPreferredName(), new DateTime(actionTime, ISOChronology.getInstanceUTC())); + } else { + builder.field(ACTION_TIME_FIELD.getPreferredName(), actionTime); + } + builder.field(STEP_FIELD.getPreferredName(), step); + if (builder.humanReadable()) { + builder.field(STEP_TIME_FIELD.getPreferredName(), new DateTime(stepTime, ISOChronology.getInstanceUTC())); + } else { + builder.field(STEP_TIME_FIELD.getPreferredName(), stepTime); + } + if (Strings.hasLength(failedStep)) { + builder.field(FAILED_STEP_FIELD.getPreferredName(), failedStep); + } + if (stepInfo != null && stepInfo.length() > 0) { + builder.rawField(STEP_INFO_FIELD.getPreferredName(), stepInfo.streamInput(), XContentType.JSON); + } + } + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(index, managedByILM, policyName, skip, lifecycleDate, phase, action, step, failedStep, phaseTime, actionTime, + stepTime, stepInfo); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj.getClass() != getClass()) { + return false; + } + IndexLifecycleExplainResponse other = (IndexLifecycleExplainResponse) obj; + return Objects.equals(index, other.index) && + Objects.equals(managedByILM, other.managedByILM) && + Objects.equals(policyName, other.policyName) && + Objects.equals(skip, other.skip) && + Objects.equals(lifecycleDate, other.lifecycleDate) && + Objects.equals(phase, other.phase) && + Objects.equals(action, other.action) && + Objects.equals(step, other.step) && + Objects.equals(failedStep, other.failedStep) && + Objects.equals(phaseTime, other.phaseTime) && + Objects.equals(actionTime, other.actionTime) && + Objects.equals(stepTime, other.stepTime) && + Objects.equals(stepInfo, other.stepInfo); + } + + @Override + public String toString() { + return Strings.toString(this, true, true); + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/RandomStepInfo.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/RandomStepInfo.java new file mode 100644 index 00000000000..3ada9fc0d02 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/RandomStepInfo.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; +import java.util.function.Supplier; + +public class RandomStepInfo implements ToXContentObject { + + private final String key; + private final String value; + + public RandomStepInfo(Supplier randomStringSupplier) { + this.key = randomStringSupplier.get(); + this.value = randomStringSupplier.get(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(key, value); + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RandomStepInfo other = (RandomStepInfo) obj; + return Objects.equals(key, other.key) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + return Strings.toString(this); + } +} \ No newline at end of file diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleRequestTests.java new file mode 100644 index 00000000000..ba1270d878e --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleRequestTests.java @@ -0,0 +1,58 @@ +/* + * 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.common.io.stream.Writeable.Reader; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction.Request; + +import java.io.IOException; + +public class ExplainLifecycleRequestTests extends AbstractWireSerializingTestCase { + + @Override + protected Request createTestInstance() { + Request request = new Request(); + if (randomBoolean()) { + request.indices(generateRandomStringArray(20, 20, false)); + } + if (randomBoolean()) { + IndicesOptions indicesOptions = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), + randomBoolean(), randomBoolean(), randomBoolean()); + request.indicesOptions(indicesOptions); + } + return request; + } + + @Override + protected Request mutateInstance(Request instance) throws IOException { + String[] indices = instance.indices(); + IndicesOptions indicesOptions = instance.indicesOptions(); + switch (between(0, 1)) { + case 0: + indices = generateRandomStringArray(20, 10, 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(); + newRequest.indices(indices); + newRequest.indicesOptions(indicesOptions); + return newRequest; + } + + @Override + protected Reader instanceReader() { + return Request::new; + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleResponseTests.java new file mode 100644 index 00000000000..9638c5a82b1 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/ExplainLifecycleResponseTests.java @@ -0,0 +1,56 @@ +/* + * 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.io.stream.NamedWriteableRegistry; +import org.elasticsearch.test.AbstractStreamableTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.DeleteAction; +import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType; +import org.elasticsearch.xpack.core.indexlifecycle.TestLifecycleType; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction.Response; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ExplainLifecycleResponseTests extends AbstractStreamableTestCase { + + @Override + protected Response createTestInstance() { + List indexResponses = new ArrayList<>(); + for (int i = 0; i < randomIntBetween(0, 2); i++) { + indexResponses.add(IndexExplainResponseTests.randomIndexExplainResponse()); + } + return new Response(indexResponses); + } + + @Override + protected Response createBlankInstance() { + return new Response(); + } + + protected NamedWriteableRegistry getNamedWriteableRegistry() { + return new NamedWriteableRegistry( + Arrays.asList(new NamedWriteableRegistry.Entry(LifecycleAction.class, DeleteAction.NAME, DeleteAction::new), + new NamedWriteableRegistry.Entry(LifecycleType.class, TestLifecycleType.TYPE, in -> TestLifecycleType.INSTANCE))); + } + + @Override + protected Response mutateInstance(Response response) { + List indexResponses = new ArrayList<>(response.getIndexResponses()); + if (indexResponses.size() > 0) { + if (randomBoolean()) { + indexResponses.add(IndexExplainResponseTests.randomIndexExplainResponse()); + } else { + indexResponses.remove(indexResponses.size() - 1); + } + } else { + indexResponses.add(IndexExplainResponseTests.randomIndexExplainResponse()); + } + return new Response(indexResponses); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexExplainResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexExplainResponseTests.java new file mode 100644 index 00000000000..8c6c7fd2194 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/action/IndexExplainResponseTests.java @@ -0,0 +1,140 @@ +/* + * 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.Strings; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.Writeable.Reader; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.elasticsearch.xpack.core.indexlifecycle.RandomStepInfo; + +import java.io.IOException; + +public class IndexExplainResponseTests extends AbstractSerializingTestCase { + + static IndexLifecycleExplainResponse randomIndexExplainResponse() { + if (frequently()) { + return randomManagedIndexExplainResponse(); + } else { + return randomUnmanagedIndexExplainResponse(); + } + } + + private static IndexLifecycleExplainResponse randomUnmanagedIndexExplainResponse() { + return IndexLifecycleExplainResponse.newUnmanagedIndexResponse(randomAlphaOfLength(10)); + } + + private static IndexLifecycleExplainResponse randomManagedIndexExplainResponse() { + return IndexLifecycleExplainResponse.newManagedIndexResponse(randomAlphaOfLength(10), randomAlphaOfLength(10), randomBoolean(), + randomNonNegativeLong(), randomAlphaOfLength(10), randomAlphaOfLength(10), randomAlphaOfLength(10), + randomBoolean() ? null : randomAlphaOfLength(10), randomNonNegativeLong(), randomNonNegativeLong(), randomNonNegativeLong(), + randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString())); + } + + @Override + protected IndexLifecycleExplainResponse createTestInstance() { + return randomIndexExplainResponse(); + } + + @Override + protected Reader instanceReader() { + return IndexLifecycleExplainResponse::new; + } + + @Override + protected IndexLifecycleExplainResponse doParseInstance(XContentParser parser) throws IOException { + return IndexLifecycleExplainResponse.PARSER.apply(parser, null); + } + + @Override + protected IndexLifecycleExplainResponse mutateInstance(IndexLifecycleExplainResponse instance) throws IOException { + String index = instance.getIndex(); + String policy = instance.getPolicyName(); + String phase = instance.getPhase(); + String action = instance.getAction(); + String step = instance.getStep(); + String failedStep = instance.getFailedStep(); + long policyTime = instance.getLifecycleDate(); + long phaseTime = instance.getPhaseTime(); + long actionTime = instance.getActionTime(); + long stepTime = instance.getStepTime(); + boolean managed = instance.managedByILM(); + boolean skip = instance.skip(); + BytesReference stepInfo = instance.getStepInfo(); + if (managed) { + switch (between(0, 12)) { + case 0: + index = index + randomAlphaOfLengthBetween(1, 5); + break; + case 1: + policy = policy + randomAlphaOfLengthBetween(1, 5); + break; + case 2: + phase = phase + randomAlphaOfLengthBetween(1, 5); + break; + case 3: + action = action + randomAlphaOfLengthBetween(1, 5); + break; + case 4: + step = step + randomAlphaOfLengthBetween(1, 5); + break; + case 5: + if (Strings.hasLength(failedStep) == false) { + failedStep = randomAlphaOfLength(10); + } else if (randomBoolean()) { + failedStep = failedStep + randomAlphaOfLengthBetween(1, 5); + } else { + failedStep = null; + } + break; + case 6: + policyTime += randomLongBetween(0, 100000); + break; + case 7: + phaseTime += randomLongBetween(0, 100000); + break; + case 8: + actionTime += randomLongBetween(0, 100000); + break; + case 9: + stepTime += randomLongBetween(0, 100000); + break; + case 10: + if (Strings.hasLength(stepInfo) == false) { + stepInfo = new BytesArray(randomByteArrayOfLength(100)); + } else if (randomBoolean()) { + stepInfo = randomValueOtherThan(stepInfo, + () -> new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString())); + } else { + stepInfo = null; + } + break; + case 11: + skip = skip == false; + break; + case 12: + return IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index); + default: + throw new AssertionError("Illegal randomisation branch"); + } + return IndexLifecycleExplainResponse.newManagedIndexResponse(index, policy, skip, policyTime, phase, action, step, failedStep, + phaseTime, actionTime, stepTime, stepInfo); + } else { + switch (between(0, 1)) { + case 0: + return IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index + randomAlphaOfLengthBetween(1, 5)); + case 1: + return randomManagedIndexExplainResponse(); + default: + throw new AssertionError("Illegal randomisation branch"); + } + } + } + +} 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 90a376e78f6..599dd3c54e6 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 @@ -35,16 +35,19 @@ 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.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.RetryAction; 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.RestRetryAction; import org.elasticsearch.xpack.indexlifecycle.action.TransportDeleteLifcycleAction; +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; @@ -143,6 +146,7 @@ public class IndexLifecycle extends Plugin implements ActionPlugin { new RestPutLifecycleAction(settings, restController), new RestGetLifecycleAction(settings, restController), new RestDeleteLifecycleAction(settings, restController), + new RestExplainLifecycleAction(settings, restController), new RestMoveToStepAction(settings, restController), new RestRetryAction(settings, restController) ); @@ -157,6 +161,7 @@ public class IndexLifecycle extends Plugin implements ActionPlugin { new ActionHandler<>(PutLifecycleAction.INSTANCE, TransportPutLifecycleAction.class), new ActionHandler<>(GetLifecycleAction.INSTANCE, TransportGetLifecycleAction.class), new ActionHandler<>(DeleteLifecycleAction.INSTANCE, TransportDeleteLifcycleAction.class), + new ActionHandler<>(ExplainLifecycleAction.INSTANCE, TransportExplainLifecycleAction.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/action/RestExplainLifecycleAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestExplainLifecycleAction.java new file mode 100644 index 00000000000..82316773ccb --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/RestExplainLifecycleAction.java @@ -0,0 +1,43 @@ +/* + * 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.support.IndicesOptions; +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.ExplainLifecycleAction; +import org.elasticsearch.xpack.indexlifecycle.IndexLifecycle; + +import java.io.IOException; + +public class RestExplainLifecycleAction extends BaseRestHandler { + + public RestExplainLifecycleAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(RestRequest.Method.GET, "_" + IndexLifecycle.NAME + "/explain", this); + controller.registerHandler(RestRequest.Method.GET, "{index}/_" + IndexLifecycle.NAME + "/explain", this); + } + + @Override + public String getName() { + return "xpack_lifecycle_explain_action"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { + String[] indexes = Strings.splitStringByCommaToArray(restRequest.param("index")); + ExplainLifecycleAction.Request explainLifecycleRequest = new ExplainLifecycleAction.Request(); + explainLifecycleRequest.indices(indexes); + explainLifecycleRequest.indicesOptions(IndicesOptions.fromRequest(restRequest, IndicesOptions.strictExpandOpen())); + + return channel -> client.execute(ExplainLifecycleAction.INSTANCE, explainLifecycleRequest, 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/TransportExplainLifecycleAction.java b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportExplainLifecycleAction.java new file mode 100644 index 00000000000..f66ad2958ce --- /dev/null +++ b/x-pack/plugin/index-lifecycle/src/main/java/org/elasticsearch/xpack/indexlifecycle/action/TransportExplainLifecycleAction.java @@ -0,0 +1,88 @@ +/* + * 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.info.TransportClusterInfoAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction.Request; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction.Response; +import org.elasticsearch.xpack.core.indexlifecycle.action.IndexLifecycleExplainResponse; + +import java.util.ArrayList; +import java.util.List; + +public class TransportExplainLifecycleAction + extends TransportClusterInfoAction { + + @Inject + public TransportExplainLifecycleAction(Settings settings, TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(settings, ExplainLifecycleAction.NAME, transportService, clusterService, threadPool, actionFilters, + ExplainLifecycleAction.Request::new, indexNameExpressionResolver); + } + + @Override + protected Response newResponse() { + return new ExplainLifecycleAction.Response(); + } + + @Override + protected String executor() { + // very lightweight operation, no need to fork + return ThreadPool.Names.SAME; + } + + @Override + protected ClusterBlockException checkBlock(ExplainLifecycleAction.Request request, ClusterState state) { + return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ, + indexNameExpressionResolver.concreteIndexNames(state, request)); + } + + @Override + protected void doMasterOperation(Request request, String[] concreteIndices, ClusterState state, ActionListener listener) { + List indexReponses = new ArrayList<>(); + for (String index : concreteIndices) { + IndexMetaData idxMetadata = state.metaData().index(index); + Settings idxSettings = idxMetadata.getSettings(); + String policyName = LifecycleSettings.LIFECYCLE_NAME_SETTING.get(idxSettings); + final IndexLifecycleExplainResponse indexResponse; + if (Strings.hasLength(policyName)) { + indexResponse = IndexLifecycleExplainResponse.newManagedIndexResponse(index, policyName, + LifecycleSettings.LIFECYCLE_SKIP_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_PHASE_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_ACTION_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_STEP_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_FAILED_STEP_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_PHASE_TIME_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_ACTION_TIME_SETTING.get(idxSettings), + LifecycleSettings.LIFECYCLE_STEP_TIME_SETTING.get(idxSettings), + new BytesArray(LifecycleSettings.LIFECYCLE_STEP_INFO_SETTING.get(idxSettings))); + } else { + indexResponse = IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index); + } + indexReponses.add(indexResponse); + } + listener.onResponse(new ExplainLifecycleAction.Response(indexReponses)); + } + +} diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/ExecuteStepsUpdateTaskTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/ExecuteStepsUpdateTaskTests.java index 46ed7a7c6d5..1312ad8b997 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/ExecuteStepsUpdateTaskTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/ExecuteStepsUpdateTaskTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.MockAction; import org.elasticsearch.xpack.core.indexlifecycle.MockStep; import org.elasticsearch.xpack.core.indexlifecycle.Phase; +import org.elasticsearch.xpack.core.indexlifecycle.RandomStepInfo; import org.elasticsearch.xpack.core.indexlifecycle.Step; import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep; @@ -181,7 +182,7 @@ public class ExecuteStepsUpdateTaskTests extends ESTestCase { public void testExecuteIncompleteWaitStepWithInfo() throws IOException { secondStep.setWillComplete(false); - IndexLifecycleRunnerTests.RandomStepInfo stepInfo = new IndexLifecycleRunnerTests.RandomStepInfo(); + RandomStepInfo stepInfo = new RandomStepInfo(() -> randomAlphaOfLength(10)); secondStep.expectedInfo(stepInfo); setStateToKey(secondStepKey); Step startStep = policyStepsRegistry.getStep(mixedPolicyName, secondStepKey); 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 d3e97065435..2d455ce0961 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 @@ -12,7 +12,6 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings.Builder; @@ -30,6 +29,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.InitializePolicyContextStep; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicyMetadata; 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.Step; import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey; import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep; @@ -240,7 +240,7 @@ public class IndexLifecycleRunnerTests extends ESTestCase { String policyName = "async_wait_policy"; StepKey stepKey = new StepKey("phase", "action", "async_wait_step"); MockAsyncWaitStep step = new MockAsyncWaitStep(stepKey, null); - RandomStepInfo stepInfo = new RandomStepInfo(); + RandomStepInfo stepInfo = new RandomStepInfo(() -> randomAlphaOfLength(10)); step.expectedInfo(stepInfo); step.setWillComplete(false); PolicyStepsRegistry stepRegistry = createOneStepPolicyStepRegistry(policyName, step); @@ -753,7 +753,8 @@ public class IndexLifecycleRunnerTests extends ESTestCase { public void testAddStepInfoToClusterState() throws IOException { String indexName = "my_index"; StepKey currentStep = new StepKey("current_phase", "current_action", "current_step"); - RandomStepInfo stepInfo = new RandomStepInfo(); + RandomStepInfo stepInfo = new RandomStepInfo(() -> randomAlphaOfLength(10)); + ClusterState clusterState = buildClusterState(indexName, Settings.builder().put(LifecycleSettings.LIFECYCLE_PHASE, currentStep.getPhase()) .put(LifecycleSettings.LIFECYCLE_ACTION, currentStep.getAction()) @@ -870,47 +871,6 @@ public class IndexLifecycleRunnerTests extends ESTestCase { LifecycleSettings.LIFECYCLE_STEP_TIME_SETTING.get(newIndexSettings)); } - static class RandomStepInfo implements ToXContentObject { - - private final String key; - private final String value; - - RandomStepInfo() { - this.key = randomAlphaOfLength(20); - this.value = randomAlphaOfLength(20); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field(key, value); - builder.endObject(); - return builder; - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - RandomStepInfo other = (RandomStepInfo) obj; - return Objects.equals(key, other.key) && Objects.equals(value, other.value); - } - - @Override - public String toString() { - return Strings.toString(this); - } - } - private static class MockAsyncActionStep extends AsyncActionStep { private Exception exception; diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.explain_lifecycle.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.explain_lifecycle.json new file mode 100644 index 00000000000..e1c1b29610a --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.index_lifecycle.explain_lifecycle.json @@ -0,0 +1,24 @@ +{ + "xpack.index_lifecycle.explain_lifecycle": { + "documentation": "http://www.elastic.co/guide/en/index_lifecycle/current/index_lifecycle.html", + "methods": [ "GET" ], + "url": { + "path": "/{index}/_index_lifecycle/explain", + "paths": ["/{index}/_index_lifecycle/explain", "/_index_lifecycle/explain"], + "parts": { + "index": { + "type" : "string", + "description" : "The name of the index to explain" + } + }, + "params": { + "human": { + "type" : "boolean", + "default" : "false", + "description" : "Return data such as dates in a human readable format" + } + } + }, + "body": null + } +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/40_explain_lifecycle.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/40_explain_lifecycle.yml new file mode 100644 index 00000000000..06272166d31 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/index_lifecycle/40_explain_lifecycle.yml @@ -0,0 +1,214 @@ +--- +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: + 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" + +--- +"Test Basic Lifecycle Explain": + + - do: + acknowledge: true + xpack.index_lifecycle.explain_lifecycle: + index: "my_index" + + - is_true: my_index.managed + - match: { my_index.index: "my_index" } + - match: { my_index.policy: "my_moveable_timeseries_lifecycle" } + - match: { my_index.phase: "new" } + - match: { my_index.action: "after" } + - match: { my_index.step: "after" } + - is_false: my_index.failed_step + - is_false: my_index.step_info + + - is_false: my_index2 + - is_false: another_index + - is_false: unmanaged_index + +--- +"Test Wildcard Index Lifecycle Explain": + + - do: + acknowledge: true + xpack.index_lifecycle.explain_lifecycle: + index: "my_*" + + - is_true: my_index.managed + - match: { my_index.index: "my_index" } + - match: { my_index.policy: "my_moveable_timeseries_lifecycle" } + - match: { my_index.phase: "new" } + - match: { my_index.action: "after" } + - match: { my_index.step: "after" } + - is_false: my_index.failed_step + - is_false: my_index.step_info + + - is_true: my_index2.managed + - match: { my_index2.index: "my_index2" } + - match: { my_index2.policy: "my_moveable_timeseries_lifecycle" } + - match: { my_index2.phase: "new" } + - match: { my_index2.action: "after" } + - match: { my_index2.step: "after" } + - is_false: my_index2.failed_step + - is_false: my_index2.step_info + + - is_false: another_index + - is_false: unmanaged_index + + +--- +"Test All Indexes Lifecycle Explain": + + - do: + acknowledge: true + xpack.index_lifecycle.explain_lifecycle: {} + + - is_true: my_index.managed + - match: { my_index.index: "my_index" } + - match: { my_index.policy: "my_moveable_timeseries_lifecycle" } + - match: { my_index.phase: "new" } + - match: { my_index.action: "after" } + - match: { my_index.step: "after" } + - is_false: my_index.failed_step + - is_false: my_index.step_info + + - is_true: my_index2.managed + - match: { my_index2.index: "my_index2" } + - match: { my_index2.policy: "my_moveable_timeseries_lifecycle" } + - match: { my_index2.phase: "new" } + - match: { my_index2.action: "after" } + - match: { my_index2.step: "after" } + - is_false: my_index2.failed_step + - is_false: my_index2.step_info + + - is_true: another_index.managed + - match: { another_index.index: "another_index" } + - match: { another_index.policy: "my_moveable_timeseries_lifecycle" } + - match: { another_index.phase: "new" } + - match: { another_index.action: "after" } + - match: { another_index.step: "after" } + - is_false: another_index.failed_step + - is_false: another_index.step_info + + - match: { unmanaged_index.index: "unmanaged_index" } + - is_false: unmanaged_index.managed + - is_false: unmanaged_index.policy + - is_false: unmanaged_index.phase + - is_false: unmanaged_index.action + - is_false: unmanaged_index.step + - is_false: another_index.failed_step + - is_false: another_index.step_info + +--- +"Test Unmanaged Index Lifecycle Explain": + + - do: + acknowledge: true + xpack.index_lifecycle.explain_lifecycle: + index: "unmanaged_index" + + - match: { unmanaged_index.index: "unmanaged_index" } + - is_false: unmanaged_index.managed + - is_false: unmanaged_index.policy + - is_false: unmanaged_index.phase + - is_false: unmanaged_index.action + - is_false: unmanaged_index.step + - is_false: another_index.failed_step + - is_false: another_index.step_info + - is_false: my_index + - is_false: my_index2 + - is_false: another_index