diff --git a/x-pack/docs/en/index-lifecycle/index.asciidoc b/x-pack/docs/en/index-lifecycle/index.asciidoc new file mode 100644 index 00000000000..5193cb5f72c --- /dev/null +++ b/x-pack/docs/en/index-lifecycle/index.asciidoc @@ -0,0 +1,2 @@ +[[xpack-index-lifecycle]] += Automating Index Properties Over Time diff --git a/x-pack/docs/en/rest-api/index-lifecycle/delete-policy.asciidoc b/x-pack/docs/en/rest-api/index-lifecycle/delete-policy.asciidoc new file mode 100644 index 00000000000..e69de29bb2d diff --git a/x-pack/docs/en/rest-api/index-lifecycle/get-policy.asciidoc b/x-pack/docs/en/rest-api/index-lifecycle/get-policy.asciidoc new file mode 100644 index 00000000000..e69de29bb2d diff --git a/x-pack/docs/en/rest-api/index-lifecycle/put-policy.asciidoc b/x-pack/docs/en/rest-api/index-lifecycle/put-policy.asciidoc new file mode 100644 index 00000000000..e69de29bb2d diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecyclePolicy.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecyclePolicy.java index 56f6ab55d91..586fdaafc9c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecyclePolicy.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/LifecyclePolicy.java @@ -8,23 +8,17 @@ package org.elasticsearch.xpack.core.indexlifecycle; import org.apache.logging.log4j.Logger; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.AbstractDiffable; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.Diffable; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.logging.ESLoggerFactory; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.Index; import java.io.IOException; import java.util.ArrayList; @@ -33,7 +27,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.Function; import java.util.function.LongSupplier; import java.util.stream.Collectors; @@ -167,12 +160,15 @@ public class LifecyclePolicy extends AbstractDiffable lastStepKey = step.getKey(); } } + + // add `after` step for phase Step.StepKey afterStepKey = new Step.StepKey(phase.getName(), null, "after"); Step phaseAfterStep = new PhaseAfterStep(nowSupplier, phase.getAfter(), afterStepKey, lastStepKey); steps.add(phaseAfterStep); lastStepKey = phaseAfterStep.getKey(); } + // init step so that policy is guaranteed to have steps.add(new InitializePolicyContextStep(new Step.StepKey("", "", ""), lastStepKey)); Collections.reverse(steps); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java new file mode 100644 index 00000000000..e39dff7b88a --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyAction.java @@ -0,0 +1,66 @@ +/* + * 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.client.Client; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * A {@link LifecycleAction} which force-merges the index. + */ +public class ReadOnlyAction implements LifecycleAction { + public static final String NAME = "readonly"; + public static final ReadOnlyAction INSTANCE = new ReadOnlyAction(); + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, + false, a -> new ReadOnlyAction()); + + public static ReadOnlyAction parse(XContentParser parser) { + return PARSER.apply(parser, null); + } + + public ReadOnlyAction() { + } + + public ReadOnlyAction(StreamInput in) { + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + } + + @Override + public List toSteps(Client client, String phase, Step.StepKey nextStepKey) { + Step.StepKey key = new Step.StepKey(phase, NAME, "readonly-step"); + return Collections.singletonList(new ReadOnlyStep(key, nextStepKey)); + } + + @Override + public String toString() { + return Strings.toString(this); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyStep.java new file mode 100644 index 00000000000..e387eb21a78 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/ReadOnlyStep.java @@ -0,0 +1,26 @@ +/* + * 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.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.Index; + +public class ReadOnlyStep extends ClusterStateActionStep { + + ReadOnlyStep(StepKey key, StepKey nextStepKey) { + super(key, nextStepKey); + } + + @Override + public ClusterState performAction(Index index, ClusterState clusterState) { + return ClusterState.builder(clusterState).metaData(MetaData.builder(clusterState.metaData()) + .updateSettings(Settings.builder().put(IndexMetaData.SETTING_BLOCKS_WRITE, true).build(), + index.getName())).build(); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/Step.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/Step.java index e7f3092de42..800c95e0a37 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/Step.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/Step.java @@ -34,7 +34,6 @@ public abstract class Step { public static class StepKey { private final String phase; - private final String action; private final String name; @@ -77,5 +76,6 @@ public abstract class Step { public String toString() { return String.format("[%s][%s][%s]", phase, action, name); } + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java index 2264fa91f3d..584e9cba1f3 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleType.java @@ -5,11 +5,12 @@ */ package org.elasticsearch.xpack.core.indexlifecycle; -import org.elasticsearch.common.Nullable; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.set.Sets; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -35,11 +36,17 @@ public class TimeseriesLifecycleType implements LifecycleType { public static final String TYPE = "timeseries"; static final List VALID_PHASES = Arrays.asList("hot", "warm", "cold", "delete"); - static final Set VALID_HOT_ACTIONS = Sets.newHashSet(RolloverAction.NAME); - static final Set VALID_WARM_ACTIONS = Sets.newHashSet(AllocateAction.NAME, ReplicasAction.NAME, - ShrinkAction.NAME, ForceMergeAction.NAME); - static final Set VALID_COLD_ACTIONS = Sets.newHashSet(AllocateAction.NAME, ReplicasAction.NAME); - static final Set VALID_DELETE_ACTIONS = Sets.newHashSet(DeleteAction.NAME); + static final List ORDERED_VALID_HOT_ACTIONS = Collections.singletonList(RolloverAction.NAME); + static final List ORDERED_VALID_WARM_ACTIONS = Arrays.asList(ReadOnlyAction.NAME, AllocateAction.NAME, + ReplicasAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME); + static final List ORDERED_VALID_COLD_ACTIONS = Arrays.asList(AllocateAction.NAME, ReplicasAction.NAME); + static final List ORDERED_VALID_DELETE_ACTIONS = Arrays.asList(DeleteAction.NAME); + static final Set VALID_HOT_ACTIONS = Sets.newHashSet(ORDERED_VALID_HOT_ACTIONS); + static final Set VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS); + static final Set VALID_COLD_ACTIONS = Sets.newHashSet(ORDERED_VALID_COLD_ACTIONS); + static final Set VALID_DELETE_ACTIONS = Sets.newHashSet(ORDERED_VALID_DELETE_ACTIONS); + private static final Phase EMPTY_WARM_PHASE = new Phase("warm", TimeValue.ZERO, + Collections.singletonMap("readonly", ReadOnlyAction.INSTANCE)); private TimeseriesLifecycleType() { } @@ -59,27 +66,42 @@ public class TimeseriesLifecycleType implements LifecycleType { } public List getOrderedPhases(Map phases) { - return VALID_PHASES.stream().map(p -> phases.getOrDefault(p, null)) - .filter(Objects::nonNull).collect(Collectors.toList()); + List orderedPhases = new ArrayList<>(VALID_PHASES.size()); + for (String phaseName : VALID_PHASES) { + Phase phase = phases.get(phaseName); + if ("warm".equals(phaseName)) { + if (phase == null) { + phase = EMPTY_WARM_PHASE; + } else if (phase.getActions().containsKey(ReadOnlyAction.NAME) == false){ + Map actionMap = new HashMap<>(phase.getActions()); + actionMap.put(ReadOnlyAction.NAME, ReadOnlyAction.INSTANCE); + phase = new Phase(phase.getName(), phase.getAfter(), actionMap); + } + } + if (phase != null) { + orderedPhases.add(phase); + } + } + return orderedPhases; } public List getOrderedActions(Phase phase) { Map actions = phase.getActions(); switch (phase.getName()) { case "hot": - return Stream.of(RolloverAction.NAME).map(a -> actions.getOrDefault(a, null)) + return ORDERED_VALID_HOT_ACTIONS.stream().map(a -> actions.getOrDefault(a, null)) .filter(Objects::nonNull).collect(Collectors.toList()); case "warm": - return Stream.of(AllocateAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME, ReplicasAction.NAME) - .map(a -> actions.getOrDefault(a, null)).filter(Objects::nonNull).collect(Collectors.toList()); + return ORDERED_VALID_WARM_ACTIONS.stream() .map(a -> actions.getOrDefault(a, null)) + .filter(Objects::nonNull).collect(Collectors.toList()); case "cold": - return Stream.of(ReplicasAction.NAME, AllocateAction.NAME).map(a -> actions.getOrDefault(a, null)) + return ORDERED_VALID_COLD_ACTIONS.stream().map(a -> actions.getOrDefault(a, null)) .filter(Objects::nonNull).collect(Collectors.toList()); case "delete": - return Stream.of(DeleteAction.NAME).map(a -> actions.getOrDefault(a, null)) + return ORDERED_VALID_DELETE_ACTIONS.stream().map(a -> actions.getOrDefault(a, null)) .filter(Objects::nonNull).collect(Collectors.toList()); default: - return Collections.emptyList(); + throw new IllegalArgumentException("lifecycle type[" + TYPE + "] does not support phase[" + phase.getName() + "]"); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockAction.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockAction.java index 6eafa8ce4d2..620edcd1479 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockAction.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockAction.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; @@ -19,62 +20,39 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; public class MockAction implements LifecycleAction { - public static final ParseField COMPLETED_FIELD = new ParseField("completed"); - public static final ParseField EXECUTED_COUNT_FIELD = new ParseField("executed_count"); public static final String NAME = "TEST_ACTION"; - private SetOnce completed = new SetOnce<>(); - private final AtomicLong executedCount; - private Exception exceptionToThrow = null; - private boolean completeOnExecute = false; - private final List steps; + private final List steps; private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(NAME, - a -> new MockAction(null, (Boolean) a[0], (Long) a[1])); + a -> new MockAction((List) a[0])); + static { - PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), COMPLETED_FIELD); - PARSER.declareLong(ConstructingObjectParser.constructorArg(), EXECUTED_COUNT_FIELD); + PARSER.declareField(ConstructingObjectParser.constructorArg(), (p, c) -> p.list(), + new ParseField("steps"), ObjectParser.ValueType.OBJECT_ARRAY); } public static MockAction parse(XContentParser parser) { return PARSER.apply(parser, null); } - public MockAction(List steps) { - this(steps, null, 0); - } - - MockAction(List steps, Boolean completed, long executedCount) { + public MockAction(List steps) { this.steps = steps; - if (completed != null) { - this.completed.set(completed); - } - this.executedCount = new AtomicLong(executedCount); } public MockAction(StreamInput in) throws IOException { - int numSteps = in.readVInt(); - this.steps = new ArrayList<>(); - for (int i = 0; i < numSteps; i++) { - // TODO(talevy): make Steps implement NamedWriteable - steps.add(null); - } - Boolean executed = in.readOptionalBoolean(); - if (executed != null) { - this.completed.set(executed); - } - this.executedCount = new AtomicLong(in.readLong()); + this.steps = in.readList(MockStep::new); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - if (completed.get() != null) { - builder.field(COMPLETED_FIELD.getPreferredName(), completed.get()); + builder.startArray("steps"); + for (MockStep step : steps) { + step.toXContent(builder, params); } - builder.field(EXECUTED_COUNT_FIELD.getPreferredName(), executedCount.get()); + builder.endArray(); builder.endObject(); return builder; } @@ -84,40 +62,23 @@ public class MockAction implements LifecycleAction { return NAME; } - @Override - public List toSteps(Client client, String phase, Step.StepKey nextStepKey) { + public List getSteps() { return steps; } - public void setCompleteOnExecute(boolean completeOnExecute) { - this.completeOnExecute = completeOnExecute; - } - - public void setExceptionToThrow(Exception exceptionToThrow) { - this.exceptionToThrow = exceptionToThrow; - } - - public boolean wasCompleted() { - return completed.get() != null && completed.get(); - } - - public void resetCompleted() { - completed = new SetOnce<>(); - } - - public long getExecutedCount() { - return executedCount.get(); + @Override + public List toSteps(Client client, String phase, Step.StepKey nextStepKey) { + return new ArrayList<>(steps); } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeOptionalBoolean(completed.get()); - out.writeLong(executedCount.get()); + out.writeList(steps); } @Override public int hashCode() { - return Objects.hash(completed.get(), executedCount.get()); + return Objects.hash(steps); } @Override @@ -129,8 +90,7 @@ public class MockAction implements LifecycleAction { return false; } MockAction other = (MockAction) obj; - return Objects.equals(completed.get(), other.completed.get()) && - Objects.equals(executedCount.get(), other.executedCount.get()); + return Objects.equals(steps, other.steps); } @Override diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockActionTests.java index c6c7aef08d1..8eb43db4b2d 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockActionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockActionTests.java @@ -5,18 +5,29 @@ */ package org.elasticsearch.xpack.core.indexlifecycle; -import org.apache.lucene.util.SetOnce; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class MockActionTests extends AbstractSerializingTestCase { @Override protected MockAction createTestInstance() { - return new MockAction(null, randomBoolean() ? null : randomBoolean(), randomLong()); + List steps = new ArrayList<>(); + int stepCount = randomIntBetween(2, 10); + Step.StepKey currentStepKey = randomStepKey(); + Step.StepKey nextStepKey = null; + for (int i = 0; i < stepCount - 1; i++) { + nextStepKey = randomStepKey(); + steps.add(new MockStep(currentStepKey, nextStepKey)); + currentStepKey = nextStepKey; + } + steps.add(new MockStep(currentStepKey, null)); + return new MockAction(steps); } @Override @@ -31,157 +42,19 @@ public class MockActionTests extends AbstractSerializingTestCase { @Override protected MockAction mutateInstance(MockAction instance) throws IOException { - boolean completed = instance.wasCompleted(); - long executedCount = instance.getExecutedCount(); - switch (randomIntBetween(0, 1)) { - case 0: - completed = completed == false; - break; - case 1: - executedCount = executedCount + randomInt(1000); - break; - default: - throw new AssertionError("Illegal randomisation branch"); + List steps = new ArrayList<>(instance.getSteps()); + MockStep lastStep = steps.remove(steps.size() - 1); + if (randomBoolean()) { + Step.StepKey additionalStepKey = randomStepKey(); + steps.add(new MockStep(lastStep.getKey(), additionalStepKey)); + steps.add(new MockStep(additionalStepKey, null)); } - return new MockAction(null, completed, executedCount); + return new MockAction(steps); } -// public void testExecuteNotComplete() { -// -// MockAction action = new MockAction(); -// action.setCompleteOnExecute(false); -// -// assertFalse(action.wasCompleted()); -// assertEquals(0L, action.getExecutedCount()); -// -// SetOnce listenerCalled = new SetOnce<>(); -// -// action.execute(null, null, null, new LifecycleAction.Listener() { -// -// @Override -// public void onSuccess(boolean completed) { -// listenerCalled.set(true); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertFalse(action.wasCompleted()); -// assertEquals(1L, action.getExecutedCount()); -// assertEquals(true, listenerCalled.get()); -// } -// -// public void testExecuteComplete() { -// -// MockAction action = new MockAction(); -// action.setCompleteOnExecute(true); -// -// assertFalse(action.wasCompleted()); -// assertEquals(0L, action.getExecutedCount()); -// -// SetOnce listenerCalled = new SetOnce<>(); -// -// action.execute(null, null, null, new LifecycleAction.Listener() { -// -// @Override -// public void onSuccess(boolean completed) { -// listenerCalled.set(true); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertTrue(action.wasCompleted()); -// assertEquals(1L, action.getExecutedCount()); -// assertEquals(true, listenerCalled.get()); -// } -// -// public void testResetComplete() { -// -// MockAction action = new MockAction(); -// action.setCompleteOnExecute(true); -// -// assertFalse(action.wasCompleted()); -// assertEquals(0L, action.getExecutedCount()); -// -// SetOnce listenerCalled = new SetOnce<>(); -// -// action.execute(null, null, null, new LifecycleAction.Listener() { -// -// @Override -// public void onSuccess(boolean completed) { -// listenerCalled.set(true); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertTrue(action.wasCompleted()); -// assertEquals(1L, action.getExecutedCount()); -// assertEquals(true, listenerCalled.get()); -// -// action.resetCompleted(); -// -// assertFalse(action.wasCompleted()); -// -// SetOnce listenerCalled2 = new SetOnce<>(); -// -// action.execute(null, null, null, new LifecycleAction.Listener() { -// -// @Override -// public void onSuccess(boolean completed) { -// listenerCalled2.set(true); -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new AssertionError("Unexpected method call", e); -// } -// }); -// -// assertTrue(action.wasCompleted()); -// assertEquals(2L, action.getExecutedCount()); -// assertEquals(true, listenerCalled2.get()); -// } -// -// public void testExecuteFailure() { -// Exception exception = new RuntimeException(); -// -// MockAction action = new MockAction(); -// action.setCompleteOnExecute(randomBoolean()); -// action.setExceptionToThrow(exception); -// -// assertFalse(action.wasCompleted()); -// assertEquals(0L, action.getExecutedCount()); -// -// SetOnce listenerCalled = new SetOnce<>(); -// -// action.execute(null, null, null, new LifecycleAction.Listener() { -// -// @Override -// public void onSuccess(boolean completed) { -// throw new AssertionError("Unexpected method call"); -// } -// -// @Override -// public void onFailure(Exception e) { -// assertSame(exception, e); -// listenerCalled.set(true); -// } -// }); -// -// assertFalse(action.wasCompleted()); -// assertEquals(1L, action.getExecutedCount()); -// assertEquals(true, listenerCalled.get()); -// } - + private Step.StepKey randomStepKey() { + return new Step.StepKey(randomAlphaOfLength(5), + randomAlphaOfLength(5), randomAlphaOfLength(5)); + } } + diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockStep.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockStep.java new file mode 100644 index 00000000000..ebe742cda29 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/MockStep.java @@ -0,0 +1,60 @@ +/* + * 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.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; + +public class MockStep extends Step implements ToXContent, Writeable { + + public MockStep(StepKey stepKey, Step.StepKey nextStepKey) { + super(stepKey, nextStepKey); + } + + public MockStep(StreamInput in) throws IOException { + super(new StepKey(in.readString(), in.readString(), in.readString()), + new StepKey(in.readString(), in.readString(), in.readString())); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(getKey().getPhase()); + out.writeString(getKey().getAction()); + out.writeString(getKey().getName()); + out.writeString(getNextStepKey().getPhase()); + out.writeString(getNextStepKey().getAction()); + out.writeString(getNextStepKey().getName()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + { + builder.startObject("step_key"); + { + builder.field("phase", getKey().getPhase()); + builder.field("action", getKey().getAction()); + builder.field("name", getKey().getName()); + } + builder.endObject(); + builder.startObject("next_step_key"); + { + builder.field("phase", getNextStepKey().getPhase()); + builder.field("action", getNextStepKey().getAction()); + builder.field("name", getNextStepKey().getName()); + } + builder.endObject(); + } + builder.endObject(); + + return builder; + } +} \ No newline at end of file diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TestLifecycleType.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TestLifecycleType.java index 9c040463fcd..f7187a53349 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TestLifecycleType.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TestLifecycleType.java @@ -35,34 +35,6 @@ public class TestLifecycleType implements LifecycleType { return TYPE; } -// @Override -// public NextActionProvider getActionProvider(IndexLifecycleContext context, Phase phase) { -// return a -> Optional.ofNullable(phase.getActions().entrySet().iterator().next()).map(Map.Entry::getValue).orElse(null); -// } - -// @Override -// public Phase getFirstPhase(Map phases) { -// return phases.values().iterator().next(); -// } -// -// @Override -// public Phase nextPhase(Map phases, @Nullable Phase currentPhase) { -// if (currentPhase == null) { -// return getFirstPhase(phases); -// } -// -// boolean foundPhase = false; -// for (Phase phase : phases.values()) { -// if (foundPhase) { -// return phase; -// } else if (phase.equals(currentPhase)) { -// foundPhase = true; -// } -// } -// -// return null; -// } - @Override public void validate(Collection phases) { // always valid diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java index c5352642f97..d4114b85c19 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/indexlifecycle/TimeseriesLifecycleTypeTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.core.indexlifecycle; +import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.test.ESTestCase; @@ -27,8 +28,13 @@ import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.LongSupplier; +import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.ORDERED_VALID_COLD_ACTIONS; +import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.ORDERED_VALID_DELETE_ACTIONS; +import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.ORDERED_VALID_HOT_ACTIONS; +import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.ORDERED_VALID_WARM_ACTIONS; import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.VALID_COLD_ACTIONS; import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.VALID_DELETE_ACTIONS; import static org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType.VALID_HOT_ACTIONS; @@ -45,363 +51,201 @@ public class TimeseriesLifecycleTypeTests extends ESTestCase { private static final ReplicasAction TEST_REPLICAS_ACTION = new ReplicasAction(1); private static final RolloverAction TEST_ROLLOVER_ACTION = new RolloverAction("", new ByteSizeValue(1), null, null); private static final ShrinkAction TEST_SHRINK_ACTION = new ShrinkAction(1); - private static final LongSupplier TEST_NOW_SUPPLIER = () -> 0L; - - public void testStub() { + private static final ReadOnlyAction TEST_READ_ONLY_ACTION = new ReadOnlyAction(); + public void testValidatePhases() { + boolean invalid = randomBoolean(); + String phaseName = randomFrom("hot", "warm", "cold", "delete"); + if (invalid) { + phaseName += randomAlphaOfLength(5); + } + Map phases = Collections.singletonMap(phaseName, + new Phase(phaseName, TimeValue.ZERO, Collections.emptyMap())); + if (invalid) { + Exception e = expectThrows(IllegalArgumentException.class, () -> TimeseriesLifecycleType.INSTANCE.validate(phases.values())); + assertThat(e.getMessage(), equalTo("Timeseries lifecycle does not support phase [" + phaseName + "]")); + } else { + TimeseriesLifecycleType.INSTANCE.validate(phases.values()); + } } -// public void testGetFirstPhase() { -// Map phases = new HashMap<>(); -// Phase expectedFirstPhase = null; -// for (String phaseName : Arrays.asList("hot", "warm", "cold", "delete")) { -// if (randomBoolean()) { -// Phase phase = new Phase(phaseName, TimeValue.MINUS_ONE, Collections.emptyMap()); -// phases.put(phaseName, phase); -// if (expectedFirstPhase == null) { -// expectedFirstPhase = phase; -// } -// } -// } -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// assertThat(policy.getFirstPhase(phases), equalTo(expectedFirstPhase)); -// } -// -// public void testGetNextPhase() { -// for (int runs = 0; runs < 20; runs++) { -// Map phases = new HashMap<>(); -// List phasesInOrder = new ArrayList<>(); -// for (String phase : VALID_PHASES) { -// if (randomBoolean()) { -// Phase phaseToAdd = new Phase(phase, TimeValue.MINUS_ONE, Collections.emptyMap()); -// phases.put(phase, phaseToAdd); -// phasesInOrder.add(phaseToAdd); -// } -// } -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// assertThat(policy.nextPhase(phases, null), equalTo(policy.getFirstPhase(phases))); -// for (int i = 0; i < phasesInOrder.size() - 1; i++) { -// assertThat(policy.nextPhase(phases, phasesInOrder.get(i)), equalTo(phasesInOrder.get(i + 1))); -// } -// if (phasesInOrder.isEmpty() == false) { -// assertNull(policy.nextPhase(phases, phasesInOrder.get(phasesInOrder.size() - 1))); -// } -// } -// } -// -// public void testValidatePhases() { -// boolean invalid = randomBoolean(); -// String phaseName = randomFrom("hot", "warm", "cold", "delete"); -// if (invalid) { -// phaseName += randomAlphaOfLength(5); -// } -// Map phases = Collections.singletonMap(phaseName, -// new Phase(phaseName, TimeValue.ZERO, Collections.emptyMap())); -// if (invalid) { -// Exception e = expectThrows(IllegalArgumentException.class, () -> TimeseriesLifecycleType.INSTANCE.validate(phases.values())); -// assertThat(e.getMessage(), equalTo("Timeseries lifecycle does not support phase [" + phaseName + "]")); -// } else { -// TimeseriesLifecycleType.INSTANCE.validate(phases.values()); -// } -// } -// -// public void testValidateHotPhase() { -// LifecycleAction invalidAction = null; -// Map actions = VALID_HOT_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// if (randomBoolean()) { -// invalidAction = getTestAction(randomFrom("allocate", "forcemerge", "delete", "replicas", "shrink")); -// actions.put(invalidAction.getWriteableName(), invalidAction); -// } -// Map hotPhase = Collections.singletonMap("hot", -// new Phase("hot", TimeValue.ZERO, actions)); -// -// if (invalidAction != null) { -// Exception e = expectThrows(IllegalArgumentException.class, -// () -> TimeseriesLifecycleType.INSTANCE.validate(hotPhase.values())); -// assertThat(e.getMessage(), -// equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [hot]")); -// } else { -// TimeseriesLifecycleType.INSTANCE.validate(hotPhase.values()); -// } -// } -// -// public void testValidateWarmPhase() { -// LifecycleAction invalidAction = null; -// Map actions = randomSubsetOf(VALID_WARM_ACTIONS) -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// if (randomBoolean()) { -// invalidAction = getTestAction(randomFrom("rollover", "delete")); -// actions.put(invalidAction.getWriteableName(), invalidAction); -// } -// Map warmPhase = Collections.singletonMap("warm", -// new Phase("warm", TimeValue.ZERO, actions)); -// -// if (invalidAction != null) { -// Exception e = expectThrows(IllegalArgumentException.class, -// () -> TimeseriesLifecycleType.INSTANCE.validate(warmPhase.values())); -// assertThat(e.getMessage(), -// equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [warm]")); -// } else { -// TimeseriesLifecycleType.INSTANCE.validate(warmPhase.values()); -// } -// } -// -// public void testValidateColdPhase() { -// LifecycleAction invalidAction = null; -// Map actions = randomSubsetOf(VALID_COLD_ACTIONS) -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// if (randomBoolean()) { -// invalidAction = getTestAction(randomFrom("rollover", "delete", "forcemerge", "shrink")); -// actions.put(invalidAction.getWriteableName(), invalidAction); -// } -// Map coldPhase = Collections.singletonMap("cold", -// new Phase("cold", TimeValue.ZERO, actions)); -// -// if (invalidAction != null) { -// Exception e = expectThrows(IllegalArgumentException.class, -// () -> TimeseriesLifecycleType.INSTANCE.validate(coldPhase.values())); -// assertThat(e.getMessage(), -// equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [cold]")); -// } else { -// TimeseriesLifecycleType.INSTANCE.validate(coldPhase.values()); -// } -// } -// -// public void testValidateDeletePhase() { -// LifecycleAction invalidAction = null; -// Map actions = VALID_DELETE_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// if (randomBoolean()) { -// invalidAction = getTestAction(randomFrom("allocate", "rollover", "replicas", "forcemerge", "shrink")); -// actions.put(invalidAction.getWriteableName(), invalidAction); -// } -// Map deletePhase = Collections.singletonMap("delete", -// new Phase("delete", TimeValue.ZERO, actions)); -// -// if (invalidAction != null) { -// Exception e = expectThrows(IllegalArgumentException.class, -// () -> TimeseriesLifecycleType.INSTANCE.validate(deletePhase.values())); -// assertThat(e.getMessage(), -// equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [delete]")); -// } else { -// TimeseriesLifecycleType.INSTANCE.validate(deletePhase.values()); -// } -// } -// -// public void testHotActionProvider() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = VALID_HOT_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions); -// MockIndexLifecycleContext context = new MockIndexLifecycleContext(indexName, "", "", -// 0, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(hotPhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, hotPhase); -// assertThat(provider.next(null), equalTo(TEST_ROLLOVER_ACTION)); -// assertNull(provider.next(TEST_ROLLOVER_ACTION)); -// } -// -// public void testWarmActionProviderWithAllActionsAndReplicasFirst() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = VALID_WARM_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// actions.put(ReplicasAction.NAME, TEST_REPLICAS_ACTION); -// Phase warmPhase = new Phase("warm", TimeValue.ZERO, actions); -// MockIndexLifecycleContext context =new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() + 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(warmPhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, warmPhase); -// if (actions.size() > 1) { -// int actionCount = 1; -// LifecycleAction current = provider.next(null); -// assertThat(current, equalTo(TEST_REPLICAS_ACTION)); -// while (actionCount++ < actions.size()) { -// current = provider.next(current); -// } -// assertNull(provider.next(current)); -// assertThat(current, equalTo(TEST_FORCE_MERGE_ACTION)); -// } else { -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// } -// -// } -// -// public void testWarmActionProviderReplicasActionSortOrder() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = randomSubsetOf(VALID_WARM_ACTIONS) -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// actions.put(ReplicasAction.NAME, TEST_REPLICAS_ACTION); -// Phase warmPhase = new Phase("warm", TimeValue.ZERO, actions); -// MockIndexLifecycleContext context =new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() + 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(warmPhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, warmPhase); -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// context = new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() - 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(warmPhase, phase); -// return true; -// } -// }; -// provider = policy.getActionProvider(context, warmPhase); -// if (actions.size() > 1) { -// int actionCount = 1; -// LifecycleAction current = provider.next(null); -// assertThat(current, not(equalTo(TEST_REPLICAS_ACTION))); -// while (actionCount++ < actions.size()) { -// current = provider.next(current); -// } -// assertNull(provider.next(current)); -// assertThat(current, equalTo(TEST_REPLICAS_ACTION)); -// } else { -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// } -// } -// -// public void testColdActionProviderAllActions() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = VALID_COLD_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// actions.put(ReplicasAction.NAME, TEST_REPLICAS_ACTION); -// Phase coldPhase = new Phase("cold", TimeValue.ZERO, actions); -// MockIndexLifecycleContext context =new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() - 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(coldPhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, coldPhase); -// if (actions.size() > 1) { -// LifecycleAction current = provider.next(null); -// assertThat(current, equalTo(TEST_ALLOCATE_ACTION)); -// assertThat(provider.next(current), equalTo(TEST_REPLICAS_ACTION)); -// } else { -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// } -// -// context = new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() + 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(coldPhase, phase); -// return true; -// } -// }; -// provider = policy.getActionProvider(context, coldPhase); -// if (actions.size() > 1) { -// LifecycleAction current = provider.next(null); -// assertThat(current, equalTo(TEST_REPLICAS_ACTION)); -// assertThat(provider.next(current), equalTo(TEST_ALLOCATE_ACTION)); -// } else { -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// } -// } -// -// public void testColdActionProviderReplicasActionSortOrder() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = randomSubsetOf(VALID_COLD_ACTIONS) -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// actions.put(ReplicasAction.NAME, TEST_REPLICAS_ACTION); -// Phase coldPhase = new Phase("cold", TimeValue.ZERO, actions); -// MockIndexLifecycleContext context =new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() + 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(coldPhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, coldPhase); -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// context = new MockIndexLifecycleContext(indexName, "", "", -// TEST_REPLICAS_ACTION.getNumberOfReplicas() - 1, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(coldPhase, phase); -// return true; -// } -// }; -// provider = policy.getActionProvider(context, coldPhase); -// if (actions.size() > 1) { -// LifecycleAction current = provider.next(null); -// assertThat(current, equalTo(TEST_ALLOCATE_ACTION)); -// assertThat(provider.next(current), equalTo(TEST_REPLICAS_ACTION)); -// } else { -// assertThat(provider.next(null), equalTo(TEST_REPLICAS_ACTION)); -// } -// } -// -// public void testDeleteActionProvider() { -// String indexName = randomAlphaOfLengthBetween(1, 10); -// Map actions = VALID_DELETE_ACTIONS -// .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); -// Phase deletePhase = new Phase("delete", TimeValue.ZERO, actions); -// -// MockIndexLifecycleContext context = new MockIndexLifecycleContext(indexName, "", "", -// 0, TEST_NOW_SUPPLIER) { -// -// @Override -// public boolean canExecute(Phase phase) { -// assertSame(deletePhase, phase); -// return true; -// } -// }; -// TimeseriesLifecycleType policy = TimeseriesLifecycleType.INSTANCE; -// LifecyclePolicy.NextActionProvider provider = policy.getActionProvider(context, deletePhase); -// assertThat(provider.next(null), equalTo(TEST_DELETE_ACTION)); -// assertNull(provider.next(TEST_DELETE_ACTION)); -// } -// -// -// private LifecycleAction getTestAction(String actionName) { -// switch (actionName) { -// case AllocateAction.NAME: -// return TEST_ALLOCATE_ACTION; -// case DeleteAction.NAME: -// return TEST_DELETE_ACTION; -// case ForceMergeAction.NAME: -// return TEST_FORCE_MERGE_ACTION; -// case ReplicasAction.NAME: -// return TEST_REPLICAS_ACTION; -// case RolloverAction.NAME: -// return TEST_ROLLOVER_ACTION; -// case ShrinkAction.NAME: -// return TEST_SHRINK_ACTION; -// default: -// throw new IllegalArgumentException("unsupported timeseries phase action [" + actionName + "]"); -// } -// } + public void testValidateHotPhase() { + LifecycleAction invalidAction = null; + Map actions = VALID_HOT_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + if (randomBoolean()) { + invalidAction = getTestAction(randomFrom("allocate", "forcemerge", "delete", "replicas", "shrink")); + actions.put(invalidAction.getWriteableName(), invalidAction); + } + Map hotPhase = Collections.singletonMap("hot", + new Phase("hot", TimeValue.ZERO, actions)); + + if (invalidAction != null) { + Exception e = expectThrows(IllegalArgumentException.class, + () -> TimeseriesLifecycleType.INSTANCE.validate(hotPhase.values())); + assertThat(e.getMessage(), + equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [hot]")); + } else { + TimeseriesLifecycleType.INSTANCE.validate(hotPhase.values()); + } + } + + public void testValidateWarmPhase() { + LifecycleAction invalidAction = null; + Map actions = randomSubsetOf(VALID_WARM_ACTIONS) + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + if (randomBoolean()) { + invalidAction = getTestAction(randomFrom("rollover", "delete")); + actions.put(invalidAction.getWriteableName(), invalidAction); + } + Map warmPhase = Collections.singletonMap("warm", + new Phase("warm", TimeValue.ZERO, actions)); + + if (invalidAction != null) { + Exception e = expectThrows(IllegalArgumentException.class, + () -> TimeseriesLifecycleType.INSTANCE.validate(warmPhase.values())); + assertThat(e.getMessage(), + equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [warm]")); + } else { + TimeseriesLifecycleType.INSTANCE.validate(warmPhase.values()); + } + } + + public void testValidateColdPhase() { + LifecycleAction invalidAction = null; + Map actions = randomSubsetOf(VALID_COLD_ACTIONS) + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + if (randomBoolean()) { + invalidAction = getTestAction(randomFrom("rollover", "delete", "forcemerge", "shrink")); + actions.put(invalidAction.getWriteableName(), invalidAction); + } + Map coldPhase = Collections.singletonMap("cold", + new Phase("cold", TimeValue.ZERO, actions)); + + if (invalidAction != null) { + Exception e = expectThrows(IllegalArgumentException.class, + () -> TimeseriesLifecycleType.INSTANCE.validate(coldPhase.values())); + assertThat(e.getMessage(), + equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [cold]")); + } else { + TimeseriesLifecycleType.INSTANCE.validate(coldPhase.values()); + } + } + + public void testValidateDeletePhase() { + LifecycleAction invalidAction = null; + Map actions = VALID_DELETE_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + if (randomBoolean()) { + invalidAction = getTestAction(randomFrom("allocate", "rollover", "replicas", "forcemerge", "shrink")); + actions.put(invalidAction.getWriteableName(), invalidAction); + } + Map deletePhase = Collections.singletonMap("delete", + new Phase("delete", TimeValue.ZERO, actions)); + + if (invalidAction != null) { + Exception e = expectThrows(IllegalArgumentException.class, + () -> TimeseriesLifecycleType.INSTANCE.validate(deletePhase.values())); + assertThat(e.getMessage(), + equalTo("invalid action [" + invalidAction.getWriteableName() + "] defined in phase [delete]")); + } else { + TimeseriesLifecycleType.INSTANCE.validate(deletePhase.values()); + } + } + + public void testGetOrderedPhases() { + Map phaseMap = new HashMap<>(); + for (String phaseName : randomSubsetOf(randomIntBetween(0, VALID_PHASES.size()), VALID_PHASES)) { + phaseMap.put(phaseName, new Phase(phaseName, TimeValue.ZERO, Collections.emptyMap())); + } + + + assertTrue(isSorted(TimeseriesLifecycleType.INSTANCE.getOrderedPhases(phaseMap), Phase::getName, VALID_PHASES)); + } + + + public void testGetOrderedActionsInvalidPhase() { + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> TimeseriesLifecycleType.INSTANCE + .getOrderedActions(new Phase("invalid", TimeValue.ZERO, Collections.emptyMap()))); + assertThat(exception.getMessage(), equalTo("lifecycle type[timeseries] does not support phase[invalid]")); + } + + public void testGetOrderedActionsHot() { + Map actions = VALID_HOT_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + Phase hotPhase = new Phase("hot", TimeValue.ZERO, actions); + List orderedActions = TimeseriesLifecycleType.INSTANCE.getOrderedActions(hotPhase); + assertTrue(isSorted(orderedActions, LifecycleAction::getWriteableName, ORDERED_VALID_HOT_ACTIONS)); + } + + public void testGetOrderedActionsWarm() { + Map actions = VALID_WARM_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + Phase warmPhase = new Phase("warm", TimeValue.ZERO, actions); + List orderedActions = TimeseriesLifecycleType.INSTANCE.getOrderedActions(warmPhase); + assertTrue(isSorted(orderedActions, LifecycleAction::getWriteableName, ORDERED_VALID_WARM_ACTIONS)); + } + + public void testGetOrderedActionsCold() { + Map actions = VALID_COLD_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + Phase coldPhase = new Phase("cold", TimeValue.ZERO, actions); + List orderedActions = TimeseriesLifecycleType.INSTANCE.getOrderedActions(coldPhase); + assertTrue(isSorted(orderedActions, LifecycleAction::getWriteableName, ORDERED_VALID_COLD_ACTIONS)); + } + + public void testGetOrderedActionsDelete() { + Map actions = VALID_DELETE_ACTIONS + .stream().map(this::getTestAction).collect(Collectors.toMap(LifecycleAction::getWriteableName, Function.identity())); + Phase deletePhase = new Phase("delete", TimeValue.ZERO, actions); + List orderedActions = TimeseriesLifecycleType.INSTANCE.getOrderedActions(deletePhase); + assertTrue(isSorted(orderedActions, LifecycleAction::getWriteableName, ORDERED_VALID_DELETE_ACTIONS)); + } + + /** + * checks whether an ordered list of objects (usually Phase and LifecycleAction) are found in the same + * order as the ordered VALID_PHASES/VALID_HOT_ACTIONS/... lists + * @param orderedObjects the ordered objects to verify sort order of + * @param getKey the way to retrieve the key to sort against (Phase#getName, LifecycleAction#getName) + * @param validOrderedKeys the source of truth of the sort order + * @param the type of object + */ + private boolean isSorted(List orderedObjects, Function getKey, List validOrderedKeys) { + int validIndex = 0; + for (T obj : orderedObjects) { + String key = getKey.apply(obj); + int i = validIndex; + for (; i < validOrderedKeys.size(); i++) { + if (validOrderedKeys.get(i).equals(key)) { + validIndex = i; + break; + } + } + if (i == validOrderedKeys.size()) { + return false; + } + } + return true; + } + + private LifecycleAction getTestAction(String actionName) { + switch (actionName) { + case AllocateAction.NAME: + return TEST_ALLOCATE_ACTION; + case DeleteAction.NAME: + return TEST_DELETE_ACTION; + case ForceMergeAction.NAME: + return TEST_FORCE_MERGE_ACTION; + case ReadOnlyAction.NAME: + return TEST_READ_ONLY_ACTION; + case ReplicasAction.NAME: + return TEST_REPLICAS_ACTION; + case RolloverAction.NAME: + return TEST_ROLLOVER_ACTION; + case ShrinkAction.NAME: + return TEST_SHRINK_ACTION; + default: + throw new IllegalArgumentException("unsupported timeseries phase action [" + actionName + "]"); + } + } } 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 d50144c6d96..e2a01bfe72b 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 @@ -41,6 +41,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.ForceMergeAction; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType; +import org.elasticsearch.xpack.core.indexlifecycle.ReadOnlyAction; import org.elasticsearch.xpack.core.indexlifecycle.ReplicasAction; import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction; @@ -138,6 +139,7 @@ public class IndexLifecycle extends Plugin implements ActionPlugin { // Lifecycle actions new NamedWriteableRegistry.Entry(LifecycleAction.class, AllocateAction.NAME, AllocateAction::new), new NamedWriteableRegistry.Entry(LifecycleAction.class, ForceMergeAction.NAME, ForceMergeAction::new), + new NamedWriteableRegistry.Entry(LifecycleAction.class, ReadOnlyAction.NAME, ReadOnlyAction::new), new NamedWriteableRegistry.Entry(LifecycleAction.class, ReplicasAction.NAME, ReplicasAction::new), new NamedWriteableRegistry.Entry(LifecycleAction.class, RolloverAction.NAME, RolloverAction::new), new NamedWriteableRegistry.Entry(LifecycleAction.class, ShrinkAction.NAME, ShrinkAction::new), @@ -156,6 +158,7 @@ public class IndexLifecycle extends Plugin implements ActionPlugin { // Lifecycle actions new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(AllocateAction.NAME), AllocateAction::parse), new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ForceMergeAction.NAME), ForceMergeAction::parse), + new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ReadOnlyAction.NAME), ReadOnlyAction::parse), new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ReplicasAction.NAME), ReplicasAction::parse), new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(RolloverAction.NAME), RolloverAction::parse), new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(ShrinkAction.NAME), ShrinkAction::parse), diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java index c18c8435da5..251cf982ed7 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/IndexLifecycleServiceTests.java @@ -267,228 +267,222 @@ public class IndexLifecycleServiceTests extends ESTestCase { assertNull(indexLifecycleService.getScheduler()); } - /** - * Checks that a new index does the following successfully: - * - * 1. setting index.lifecycle.date - * 2. sets phase - * 3. sets action - * 4. executes action - */ - @SuppressWarnings("unchecked") - public void testTriggeredWithMatchingPolicy() { - String policyName = randomAlphaOfLengthBetween(1, 20); - MockAction mockAction = new MockAction(Collections.emptyList()); - Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); - LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, - Collections.singletonMap(phase.getName(), phase)); - SortedMap policyMap = new TreeMap<>(); - policyMap.put(policyName, policy); - Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) - .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); - ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() - .fPut(index.getName(), indexMetadata); - MetaData metaData = MetaData.builder() - .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) - .indices(indices.build()) - .persistentSettings(settings(Version.CURRENT).build()) - .build(); - ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) - .metaData(metaData) - .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) - .build(); +// /** +// * Checks that a new index does the following successfully: +// * +// * 1. setting index.lifecycle.date +// * 2. sets phase +// * 3. sets action +// * 4. executes action +// */ +// @SuppressWarnings("unchecked") +// public void testTriggeredWithMatchingPolicy() { +// String policyName = randomAlphaOfLengthBetween(1, 20); +// MockAction mockAction = new MockAction(Collections.emptyList()); +// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); +// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, +// Collections.singletonMap(phase.getName(), phase)); +// SortedMap policyMap = new TreeMap<>(); +// policyMap.put(policyName, policy); +// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) +// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); +// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() +// .fPut(index.getName(), indexMetadata); +// MetaData metaData = MetaData.builder() +// .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) +// .indices(indices.build()) +// .persistentSettings(settings(Version.CURRENT).build()) +// .build(); +// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) +// .metaData(metaData) +// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) +// .build(); +// +// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); +// +// when(clusterService.state()).thenReturn(currentState); +// +// SetOnce dateUpdated = new SetOnce<>(); +// SetOnce phaseUpdated = new SetOnce<>(); +// SetOnce actionUpdated = new SetOnce<>(); +// doAnswer(invocationOnMock -> { +// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() +// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), +// indexMetadata.getCreationDate()).build(), index.getName()); +// dateUpdated.set(true); +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// }).doAnswer(invocationOnMock -> { +// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() +// .put(LifecycleSettings.LIFECYCLE_ACTION, "") +// .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, -1L) +// .put(LifecycleSettings.LIFECYCLE_PHASE_TIME, now) +// .put(LifecycleSettings.LIFECYCLE_PHASE, "phase").build(), index.getName()); +// phaseUpdated.set(true); +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// }).doAnswer(invocationOnMock -> { +// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() +// .put(LifecycleSettings.LIFECYCLE_ACTION, MockAction.NAME) +// .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, now).build(), index.getName()); +// actionUpdated.set(true); +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// }).when(indicesClient).updateSettings(any(), any()); +// +// indexLifecycleService.triggered(schedulerEvent); +// +// assertThat(dateUpdated.get(), equalTo(true)); +// assertThat(phaseUpdated.get(), equalTo(true)); +// assertThat(actionUpdated.get(), equalTo(true)); +// } - SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); +// /** +// * Check that a policy is executed without first setting the `index.lifecycle.date` setting +// */ +// @SuppressWarnings("unchecked") +// public void testTriggeredWithDateSettingAlreadyPresent() { +// String policyName = randomAlphaOfLengthBetween(1, 20); +// MockAction mockAction = new MockAction(Collections.emptyList()); +// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); +// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, +// Collections.singletonMap(phase.getName(), phase)); +// SortedMap policyMap = new TreeMap<>(); +// policyMap.put(policyName, policy); +// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// long creationDate = randomLongBetween(0, now - 1); +// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) +// .settings(settings(Version.CURRENT) +// .put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName) +// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), creationDate)) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).creationDate(creationDate).build(); +// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() +// .fPut(index.getName(), indexMetadata); +// MetaData metaData = MetaData.builder() +// .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) +// .indices(indices.build()) +// .persistentSettings(settings(Version.CURRENT).build()) +// .build(); +// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) +// .metaData(metaData) +// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) +// .build(); +// +// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); +// +// when(clusterService.state()).thenReturn(currentState); +// +// SetOnce dateUpdated = new SetOnce<>(); +// doAnswer(invocationOnMock -> { +// UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// try { +// UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() +// .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), +// indexMetadata.getCreationDate()).build(), index.getName()); +// dateUpdated.set(true); +// } catch (AssertionError e) { +// // noop: here because we are either updating the phase or action prior to executing MockAction +// } +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// }).when(indicesClient).updateSettings(any(), any()); +// +// indexLifecycleService.triggered(schedulerEvent); +// +// assertNull(dateUpdated.get()); +// } - when(clusterService.state()).thenReturn(currentState); +// /** +// * Check that if an index has an unknown lifecycle policy set it does not +// * execute any policy but does process other indexes. +// */ +// public void testTriggeredUnknownPolicyNameSet() { +// String policyName = randomAlphaOfLengthBetween(1, 20); +// MockAction mockAction = new MockAction(Collections.emptyList()); +// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); +// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, +// Collections.singletonMap(phase.getName(), phase)); +// SortedMap policyMap = new TreeMap<>(); +// policyMap.put(policyName, policy); +// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) +// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), "foo")) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); +// Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) +// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); +// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), +// indexMetadata).fPut(index2.getName(), indexMetadata2); +// MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) +// .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); +// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) +// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); +// +// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); +// +// when(clusterService.state()).thenReturn(currentState); +// +// doAnswer(invocationOnMock -> { +// @SuppressWarnings("unchecked") +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// +// }).when(indicesClient).updateSettings(any(), any()); +// +// indexLifecycleService.triggered(schedulerEvent); +// } - SetOnce dateUpdated = new SetOnce<>(); - SetOnce phaseUpdated = new SetOnce<>(); - SetOnce actionUpdated = new SetOnce<>(); - doAnswer(invocationOnMock -> { - UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() - .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), - indexMetadata.getCreationDate()).build(), index.getName()); - dateUpdated.set(true); - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - }).doAnswer(invocationOnMock -> { - UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() - .put(LifecycleSettings.LIFECYCLE_ACTION, "") - .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, -1L) - .put(LifecycleSettings.LIFECYCLE_PHASE_TIME, now) - .put(LifecycleSettings.LIFECYCLE_PHASE, "phase").build(), index.getName()); - phaseUpdated.set(true); - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - }).doAnswer(invocationOnMock -> { - UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() - .put(LifecycleSettings.LIFECYCLE_ACTION, MockAction.NAME) - .put(LifecycleSettings.LIFECYCLE_ACTION_TIME, now).build(), index.getName()); - actionUpdated.set(true); - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - }).when(indicesClient).updateSettings(any(), any()); - - indexLifecycleService.triggered(schedulerEvent); - - assertThat(dateUpdated.get(), equalTo(true)); - assertThat(phaseUpdated.get(), equalTo(true)); - assertThat(actionUpdated.get(), equalTo(true)); - assertThat(mockAction.getExecutedCount(), equalTo(1L)); - } - - /** - * Check that a policy is executed without first setting the `index.lifecycle.date` setting - */ - @SuppressWarnings("unchecked") - public void testTriggeredWithDateSettingAlreadyPresent() { - String policyName = randomAlphaOfLengthBetween(1, 20); - MockAction mockAction = new MockAction(Collections.emptyList()); - Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); - LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, - Collections.singletonMap(phase.getName(), phase)); - SortedMap policyMap = new TreeMap<>(); - policyMap.put(policyName, policy); - Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - long creationDate = randomLongBetween(0, now - 1); - IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) - .settings(settings(Version.CURRENT) - .put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName) - .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), creationDate)) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).creationDate(creationDate).build(); - ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder() - .fPut(index.getName(), indexMetadata); - MetaData metaData = MetaData.builder() - .putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) - .indices(indices.build()) - .persistentSettings(settings(Version.CURRENT).build()) - .build(); - ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT) - .metaData(metaData) - .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) - .build(); - - SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); - - when(clusterService.state()).thenReturn(currentState); - - SetOnce dateUpdated = new SetOnce<>(); - doAnswer(invocationOnMock -> { - UpdateSettingsRequest request = (UpdateSettingsRequest) invocationOnMock.getArguments()[0]; - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - try { - UpdateSettingsTestHelper.assertSettingsRequest(request, Settings.builder() - .put(LifecycleSettings.LIFECYCLE_INDEX_CREATION_DATE_SETTING.getKey(), - indexMetadata.getCreationDate()).build(), index.getName()); - dateUpdated.set(true); - } catch (AssertionError e) { - // noop: here because we are either updating the phase or action prior to executing MockAction - } - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - }).when(indicesClient).updateSettings(any(), any()); - - indexLifecycleService.triggered(schedulerEvent); - - assertNull(dateUpdated.get()); - assertThat(mockAction.getExecutedCount(), equalTo(1L)); - } - - /** - * Check that if an index has an unknown lifecycle policy set it does not - * execute any policy but does process other indexes. - */ - public void testTriggeredUnknownPolicyNameSet() { - String policyName = randomAlphaOfLengthBetween(1, 20); - MockAction mockAction = new MockAction(Collections.emptyList()); - Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); - LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, - Collections.singletonMap(phase.getName(), phase)); - SortedMap policyMap = new TreeMap<>(); - policyMap.put(policyName, policy); - Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()) - .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), "foo")) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); - Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) - .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); - ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), - indexMetadata).fPut(index2.getName(), indexMetadata2); - MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) - .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); - ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) - .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); - - SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); - - when(clusterService.state()).thenReturn(currentState); - - doAnswer(invocationOnMock -> { - @SuppressWarnings("unchecked") - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - - }).when(indicesClient).updateSettings(any(), any()); - - indexLifecycleService.triggered(schedulerEvent); - - assertThat(mockAction.getExecutedCount(), equalTo(1L)); - } - - /** - * Check that if an index has no lifecycle policy set it does not execute - * any policy but does process other indexes. - */ - public void testTriggeredNoPolicyNameSet() { - String policyName = randomAlphaOfLengthBetween(1, 20); - MockAction mockAction = new MockAction(Collections.emptyList()); - Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); - LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, - Collections.singletonMap(phase.getName(), phase)); - SortedMap policyMap = new TreeMap<>(); - policyMap.put(policyName, policy); - Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()).settings(settings(Version.CURRENT)) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); - Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) - .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) - .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); - ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), - indexMetadata).fPut(index2.getName(), indexMetadata2); - MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) - .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); - ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) - .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); - - SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); - - when(clusterService.state()).thenReturn(currentState); - - doAnswer(invocationOnMock -> { - @SuppressWarnings("unchecked") - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; - listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); - return null; - - }).when(indicesClient).updateSettings(any(), any()); - - indexLifecycleService.triggered(schedulerEvent); - - assertThat(mockAction.getExecutedCount(), equalTo(1L)); - } +// /** +// * Check that if an index has no lifecycle policy set it does not execute +// * any policy but does process other indexes. +// */ +// public void testTriggeredNoPolicyNameSet() { +// String policyName = randomAlphaOfLengthBetween(1, 20); +// MockAction mockAction = new MockAction(Collections.emptyList()); +// Phase phase = new Phase("phase", TimeValue.ZERO, Collections.singletonMap("action", mockAction)); +// LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, policyName, +// Collections.singletonMap(phase.getName(), phase)); +// SortedMap policyMap = new TreeMap<>(); +// policyMap.put(policyName, policy); +// Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// IndexMetaData indexMetadata = IndexMetaData.builder(index.getName()).settings(settings(Version.CURRENT)) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); +// Index index2 = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); +// IndexMetaData indexMetadata2 = IndexMetaData.builder(index2.getName()) +// .settings(settings(Version.CURRENT).put(LifecycleSettings.LIFECYCLE_NAME_SETTING.getKey(), policyName)) +// .numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build(); +// ImmutableOpenMap.Builder indices = ImmutableOpenMap. builder().fPut(index.getName(), +// indexMetadata).fPut(index2.getName(), indexMetadata2); +// MetaData metaData = MetaData.builder().putCustom(IndexLifecycleMetadata.TYPE, new IndexLifecycleMetadata(policyMap)) +// .indices(indices.build()).persistentSettings(settings(Version.CURRENT).build()).build(); +// ClusterState currentState = ClusterState.builder(ClusterName.DEFAULT).metaData(metaData) +// .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()).build(); +// +// SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event(IndexLifecycle.NAME, randomLong(), randomLong()); +// +// when(clusterService.state()).thenReturn(currentState); +// +// doAnswer(invocationOnMock -> { +// @SuppressWarnings("unchecked") +// ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; +// listener.onResponse(UpdateSettingsTestHelper.createMockResponse(true)); +// return null; +// +// }).when(indicesClient).updateSettings(any(), any()); +// +// indexLifecycleService.triggered(schedulerEvent); +// } public void testTriggeredDifferentJob() { SchedulerEngine.Event schedulerEvent = new SchedulerEngine.Event("foo", randomLong(), randomLong()); diff --git a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/LifecyclePolicyTests.java b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/LifecyclePolicyTests.java index 5e310aae5a1..e36259ce162 100644 --- a/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/LifecyclePolicyTests.java +++ b/x-pack/plugin/index-lifecycle/src/test/java/org/elasticsearch/xpack/indexlifecycle/LifecyclePolicyTests.java @@ -14,13 +14,14 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.AbstractSerializingTestCase; -import org.elasticsearch.xpack.core.indexlifecycle.DeleteAction; -import org.elasticsearch.xpack.core.indexlifecycle.DeleteAsyncActionStep; +import org.elasticsearch.xpack.core.indexlifecycle.InitializePolicyContextStep; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction; import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType; 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.PhaseAfterStep; import org.elasticsearch.xpack.core.indexlifecycle.Step; import org.elasticsearch.xpack.core.indexlifecycle.TestLifecycleType; import org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType; @@ -40,15 +41,7 @@ import static org.mockito.Mockito.mock; public class LifecyclePolicyTests extends AbstractSerializingTestCase { - private String indexName; private String lifecycleName; - private MockAction firstAction; - private MockAction secondAction; - private MockAction thirdAction; - private Phase firstPhase; - private Phase secondPhase; - private Phase thirdPhase; - private LifecyclePolicy policy; @Override protected LifecyclePolicy doParseInstance(XContentParser parser) throws IOException { @@ -58,14 +51,14 @@ public class LifecyclePolicyTests extends AbstractSerializingTestCase TestLifecycleType.INSTANCE))); } @Override protected NamedXContentRegistry xContentRegistry() { List entries = new ArrayList<>(ClusterModule.getNamedXWriteables()); - entries.add(new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(DeleteAction.NAME), DeleteAction::parse)); + entries.add(new NamedXContentRegistry.Entry(LifecycleAction.class, new ParseField(MockAction.NAME), MockAction::parse)); entries.add(new NamedXContentRegistry.Entry(LifecycleType.class, new ParseField(TestLifecycleType.TYPE), (p) -> TestLifecycleType.INSTANCE)); return new NamedXContentRegistry(entries); @@ -73,13 +66,14 @@ public class LifecyclePolicyTests extends AbstractSerializingTestCase phases = new HashMap<>(numberPhases); for (int i = 0; i < numberPhases; i++) { TimeValue after = TimeValue.parseTimeValue(randomTimeValue(0, 1000000000, "s", "m", "h", "d"), "test_after"); Map actions = new HashMap<>(); if (randomBoolean()) { - DeleteAction action = new DeleteAction(); + MockAction action = new MockAction(Collections.emptyList()); actions.put(action.getWriteableName(), action); } String phaseName = randomAlphaOfLength(10); @@ -117,26 +111,64 @@ public class LifecyclePolicyTests extends AbstractSerializingTestCase 0L; - Step deleteStep = new DeleteAsyncActionStep( - new Step.StepKey("delete", "DELETE", "delete"), null, client); + MockStep firstStep = new MockStep(new Step.StepKey("test", "test", "test"), null); - indexName = randomAlphaOfLengthBetween(1, 20); lifecycleName = randomAlphaOfLengthBetween(1, 20); Map phases = new LinkedHashMap<>(); - firstAction = new MockAction(Arrays.asList(deleteStep)); + LifecycleAction firstAction = new MockAction(Arrays.asList(firstStep)); Map actions = Collections.singletonMap(MockAction.NAME, firstAction); - firstPhase = new Phase("delete", TimeValue.ZERO, actions); + Phase firstPhase = new Phase("test", TimeValue.ZERO, actions); phases.put(firstPhase.getName(), firstPhase); - policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, lifecycleName, phases); + LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, lifecycleName, phases); List steps = policy.toSteps(client, nowSupplier); - assertThat(steps.size(), equalTo(2)); - assertThat(steps.get(0).getKey(), equalTo(new Step.StepKey("delete", null, "after"))); - assertThat(steps.get(0).getNextStepKey(), equalTo(deleteStep.getKey())); - assertThat(steps.get(1), equalTo(deleteStep)); - assertNull(steps.get(1).getNextStepKey()); + assertThat(steps.size(), equalTo(3)); + assertThat(steps.get(0).getKey(), equalTo(new Step.StepKey("", "", ""))); + assertThat(steps.get(0).getNextStepKey(), equalTo(new Step.StepKey("test", null, "after"))); + assertThat(steps.get(1).getKey(), equalTo(new Step.StepKey("test", null, "after"))); + assertThat(steps.get(1).getNextStepKey(), equalTo(firstStep.getKey())); + assertThat(steps.get(2), equalTo(firstStep)); + assertNull(steps.get(2).getNextStepKey()); + } + + public void testToStepsWithTwoPhases() { + Client client = mock(Client.class); + LongSupplier nowSupplier = () -> 0L; + MockStep secondActionStep = new MockStep(new Step.StepKey("second_phase", "test", "test"), null); + MockStep secondAfter = new MockStep(new Step.StepKey("second_phase", null, "after"), secondActionStep.getKey()); + MockStep firstActionAnotherStep = new MockStep(new Step.StepKey("first_phase", "test", "test"), secondAfter.getKey()); + MockStep firstActionStep = new MockStep(new Step.StepKey("first_phase", "test", "test"), firstActionAnotherStep.getKey()); + MockStep firstAfter = new MockStep(new Step.StepKey("first_phase", null, "after"), firstActionStep.getKey()); + MockStep init = new MockStep(new Step.StepKey("", "", ""), firstAfter.getKey()); + + lifecycleName = randomAlphaOfLengthBetween(1, 20); + Map phases = new LinkedHashMap<>(); + LifecycleAction firstAction = new MockAction(Arrays.asList(firstActionStep, firstActionAnotherStep)); + LifecycleAction secondAction = new MockAction(Arrays.asList(secondActionStep)); + Map firstActions = Collections.singletonMap(MockAction.NAME, firstAction); + Map secondActions = Collections.singletonMap(MockAction.NAME, secondAction); + Phase firstPhase = new Phase("first_phase", TimeValue.ZERO, firstActions); + Phase secondPhase = new Phase("second_phase", TimeValue.ZERO, secondActions); + phases.put(firstPhase.getName(), firstPhase); + phases.put(secondPhase.getName(), secondPhase); + LifecyclePolicy policy = new LifecyclePolicy(TestLifecycleType.INSTANCE, lifecycleName, phases); + + List steps = policy.toSteps(client, nowSupplier); + assertThat(steps.size(), equalTo(6)); + assertThat(steps.get(0).getClass(), equalTo(InitializePolicyContextStep.class)); + assertThat(steps.get(0).getKey(), equalTo(init.getKey())); + assertThat(steps.get(0).getNextStepKey(), equalTo(init.getNextStepKey())); + assertThat(steps.get(1).getClass(), equalTo(PhaseAfterStep.class)); + assertThat(steps.get(1).getKey(), equalTo(firstAfter.getKey())); + assertThat(steps.get(1).getNextStepKey(), equalTo(firstAfter.getNextStepKey())); + assertThat(steps.get(2), equalTo(firstActionStep)); + assertThat(steps.get(3), equalTo(firstActionAnotherStep)); + assertThat(steps.get(4).getClass(), equalTo(PhaseAfterStep.class)); + assertThat(steps.get(4).getKey(), equalTo(secondAfter.getKey())); + assertThat(steps.get(4).getNextStepKey(), equalTo(secondAfter.getNextStepKey())); + assertThat(steps.get(5), equalTo(secondActionStep)); } }