Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ public List<Step> toSteps(Client client, String phase, StepKey nextStepKey) {
return Arrays.asList(allocateStep, routedCheckStep);
}

@Override
public List<StepKey> toStepKeys(String phase) {
StepKey allocateKey = new StepKey(phase, NAME, NAME);
StepKey allocationRoutedKey = new StepKey(phase, NAME, AllocationRoutedStep.NAME);
return Arrays.asList(allocateKey, allocationRoutedKey);
}

@Override
public int hashCode() {
return Objects.hash(include, exclude, require);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;

import java.io.IOException;
import java.util.Collections;
Expand Down Expand Up @@ -62,6 +63,11 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
return Collections.singletonList(new DeleteStep(deleteStepKey, nextStepKey, client));
}

@Override
public List<StepKey> toStepKeys(String phase) {
return Collections.singletonList(new Step.StepKey(phase, NAME, DeleteStep.NAME));
}

@Override
public int hashCode() {
return 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,20 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws

@Override
public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey) {
StepKey updateCompressionKey = new StepKey(phase, NAME, "best_compression");
StepKey forceMergeKey = new StepKey(phase, NAME, ForceMergeStep.NAME);
StepKey countKey = new StepKey(phase, NAME, SegmentCountStep.NAME);
ForceMergeStep forceMergeStep = new ForceMergeStep(forceMergeKey, countKey, client, maxNumSegments);
SegmentCountStep segmentCountStep = new SegmentCountStep(countKey, nextStepKey, client, maxNumSegments);
return Arrays.asList(forceMergeStep, segmentCountStep);
}

@Override
public List<StepKey> toStepKeys(String phase) {
StepKey forceMergeKey = new StepKey(phase, NAME, ForceMergeStep.NAME);
StepKey countKey = new StepKey(phase, NAME, SegmentCountStep.NAME);
return Arrays.asList(forceMergeKey, countKey);
}

@Override
public int hashCode() {
return Objects.hash(maxNumSegments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;

import java.util.List;

Expand All @@ -29,6 +30,15 @@ public interface LifecycleAction extends ToXContentObject, NamedWriteable {
*/
List<Step> toSteps(Client client, String phase, @Nullable Step.StepKey nextStepKey);

/**
*
* @param phase
* the name of the phase this action is being executed within
* @return the {@link StepKey}s for the steps which will be executed in this
* action
*/
List<StepKey> toStepKeys(String phase);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we find a way to do this so that either toSteps is derived from toStepKeys, or vice-versa? The separation of these in ways that need to be kept consistent sounds like it may be difficult to keep in sync

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I thought about this at the time but the problem is that in order to derive the steps from the step keys we would end up having some ugly if-else logic to iterate through the keys returned by toStepKeys and for each try to work out which step it is based on and build that step. The reverse of building the stepKeys based on the output of toSteps meant passing in a null client in production code which I also didn't like because it could potentially cause NPE's. In order to try to keep consistency between the two methods AbstractActionTestCase has a method which create a random instance of the action under test and makes sure that the step keys from the steps generated by toSteps() are the same as the step keys created by toStepKeys(): https://github.com/elastic/elasticsearch/pull/32283/files#diff-99c08c350d9cf07e3a5366ab534f80f7R28

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, then I guess that is the best we can do for now. I do not see a better way at the moment either


/**
* @return true if this action is considered safe. An action is not safe if
* it will produce unwanted side effects or will get stuck when the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,82 @@ public boolean isActionSafe(StepKey stepKey) {
}
}

/**
* Finds the next valid {@link StepKey} on or after the provided
* {@link StepKey}. If the provided {@link StepKey} is valid in this policy
* it will be returned. If its not valid the next available {@link StepKey}
* will be returned.
*/
public StepKey getNextValidStep(StepKey stepKey) {
Phase phase = phases.get(stepKey.getPhase());
if (phase == null) {
// Phase doesn't exist so find the after step for the previous
// available phase
return getAfterStepBeforePhase(stepKey.getPhase());
} else {
// Phase exists so check if the action exists
LifecycleAction action = phase.getActions().get(stepKey.getAction());
if (action == null) {
// if action doesn't exist find the first step in the next
// available action
return getFirstStepInNextAction(stepKey.getAction(), phase);
} else {
// if the action exists check if the step itself exists
if (action.toStepKeys(phase.getName()).contains(stepKey)) {
// stepKey is valid still so return it
return stepKey;
} else {
// stepKey no longer exists in the action so we need to move
// to the first step in the next action since skipping steps
// in an action is not safe
return getFirstStepInNextAction(stepKey.getAction(), phase);
}
}
}
}

private StepKey getNextAfterStep(String currentPhaseName) {
String nextPhaseName = type.getNextPhaseName(currentPhaseName, phases);
if (nextPhaseName == null) {
// We don't have a next phase after this one so there is no after
// step to move to. Instead we need to go to the terminal step as
// there are no more steps we should execute
return TerminalPolicyStep.KEY;
} else {
return new StepKey(currentPhaseName, PhaseAfterStep.NAME, PhaseAfterStep.NAME);
}
}

private StepKey getAfterStepBeforePhase(String currentPhaseName) {
String nextPhaseName = type.getNextPhaseName(currentPhaseName, phases);
if (nextPhaseName == null) {
// We don't have a next phase after this one so the next step is the
// TerminalPolicyStep
return TerminalPolicyStep.KEY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow why the step before a phase would be the terminal step?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I think I get it. you are looking for the after-step for the next phase, but there is no next phase, so we go straight to terminalpolicystep. If this is the case, some extra docs would help make that clearer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are correct, I'll try to clarify it better int eh comment

} else {
String prevPhaseName = type.getPreviousPhaseName(currentPhaseName, phases);
if (prevPhaseName == null) {
// no previous phase available so go to the
// InitializePolicyContextStep
return InitializePolicyContextStep.KEY;
}
return new StepKey(prevPhaseName, PhaseAfterStep.NAME, PhaseAfterStep.NAME);
}
}

private StepKey getFirstStepInNextAction(String currentActionName, Phase phase) {
String nextActionName = type.getNextActionName(currentActionName, phase);
if (nextActionName == null) {
// The current action is the last in this phase so we need to find
// the next after step
return getNextAfterStep(phase.getName());
} else {
LifecycleAction nextAction = phase.getActions().get(nextActionName);
// Return the first stepKey for nextAction
return nextAction.toStepKeys(phase.getName()).get(0);
}
}

@Override
public int hashCode() {
return Objects.hash(name, phases);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.elasticsearch.common.io.stream.NamedWriteable;

import java.security.Policy;
import java.util.Collection;
import java.util.List;
import java.util.Map;
Expand All @@ -18,8 +19,47 @@ public interface LifecycleType extends NamedWriteable {
*/
List<Phase> getOrderedPhases(Map<String, Phase> phases);

/**
* Returns the next phase thats available after
* <code>currentPhaseName</code>. Note that <code>currentPhaseName</code>
* does not need to exist in <code>phases</code>.
*
* If the current {@link Phase} is the last phase in the {@link Policy} this
* method will return <code>null</code>.
*
* If the phase is not valid for the lifecycle type an
* {@link IllegalArgumentException} will be thrown.
*/
String getNextPhaseName(String currentPhaseName, Map<String, Phase> phases);

/**
* Returns the previous phase thats available before
* <code>currentPhaseName</code>. Note that <code>currentPhaseName</code>
* does not need to exist in <code>phases</code>.
*
* If the current {@link Phase} is the first phase in the {@link Policy}
* this method will return <code>null</code>.
*
* If the phase is not valid for the lifecycle type an
* {@link IllegalArgumentException} will be thrown.
*/
String getPreviousPhaseName(String currentPhaseName, Map<String, Phase> phases);

List<LifecycleAction> getOrderedActions(Phase phase);

/**
* Returns the name of the next phase that is available in the phases after
* <code>currentActionName</code>. Note that <code>currentActionName</code>
* does not need to exist in the {@link Phase}.
*
* If the current action is the last action in the phase this method will
* return <code>null</code>.
*
* If the action is not valid for the phase an
* {@link IllegalArgumentException} will be thrown.
*/
String getNextActionName(String currentActionName, Phase phase);


/**
* validates whether the specified <code>phases</code> are valid for this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;

import java.io.IOException;
import java.util.Collections;
Expand Down Expand Up @@ -65,6 +66,11 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
Settings readOnlySettings = Settings.builder().put(IndexMetaData.SETTING_BLOCKS_WRITE, true).build();
return Collections.singletonList(new UpdateSettingsStep(key, nextStepKey, client, readOnlySettings));
}

@Override
public List<StepKey> toStepKeys(String phase) {
return Collections.singletonList(new Step.StepKey(phase, NAME, NAME));
}

@Override
public int hashCode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
new ReplicasAllocatedStep(enoughKey, nextStepKey));
}

@Override
public List<StepKey> toStepKeys(String phase) {
StepKey updateReplicasKey = new StepKey(phase, NAME, UpdateSettingsStep.NAME);
StepKey enoughKey = new StepKey(phase, NAME, ReplicasAllocatedStep.NAME);
return Arrays.asList(updateReplicasKey, enoughKey);
}

public int getNumberOfReplicas() {
return numberOfReplicas;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
return Arrays.asList(rolloverStep, updateDateStep);
}

@Override
public List<StepKey> toStepKeys(String phase) {
StepKey rolloverStepKey = new StepKey(phase, NAME, RolloverStep.NAME);
StepKey updateDateStepKey = new StepKey(phase, NAME, UpdateRolloverLifecycleDateStep.NAME);
return Arrays.asList(rolloverStepKey, updateDateStepKey);
}

@Override
public int hashCode() {
return Objects.hash(maxSize, maxAge, maxDocs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey)
return Arrays.asList(setSingleNodeStep, allocationStep, shrink, allocated, aliasSwapAndDelete, waitOnShrinkTakeover);
}

@Override
public List<StepKey> toStepKeys(String phase) {
StepKey setSingleNodeKey = new StepKey(phase, NAME, SetSingleNodeAllocateStep.NAME);
StepKey allocationRoutedKey = new StepKey(phase, NAME, AllocationRoutedStep.NAME);
StepKey shrinkKey = new StepKey(phase, NAME, ShrinkStep.NAME);
StepKey enoughShardsKey = new StepKey(phase, NAME, ShrunkShardsAllocatedStep.NAME);
StepKey aliasKey = new StepKey(phase, NAME, ShrinkSetAliasStep.NAME);
StepKey isShrunkIndexKey = new StepKey(phase, NAME, ShrunkenIndexCheckStep.NAME);
return Arrays.asList(setSingleNodeKey, allocationRoutedKey, shrinkKey, enoughShardsKey, aliasKey, isShrunkIndexKey);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,48 @@ public List<Phase> getOrderedPhases(Map<String, Phase> phases) {
return orderedPhases;
}

@Override
public String getNextPhaseName(String currentPhaseName, Map<String, Phase> phases) {
int index = VALID_PHASES.indexOf(currentPhaseName);
if (index < 0 && "new".equals(currentPhaseName) == false) {
throw new IllegalArgumentException("[" + currentPhaseName + "] is not a valid phase for lifecycle type [" + TYPE + "]");
} else {
// Find the next phase after `index` that exists in `phases` and return it
while (++index < VALID_PHASES.size()) {
String phaseName = VALID_PHASES.get(index);
if (phases.containsKey(phaseName)) {
return phaseName;
}
}
// if we have exhausted VALID_PHASES and haven't found a matching
// phase in `phases` return null indicating there is no next phase
// available
return null;
}
}

public String getPreviousPhaseName(String currentPhaseName, Map<String, Phase> phases) {
if ("new".equals(currentPhaseName)) {
return null;
}
int index = VALID_PHASES.indexOf(currentPhaseName);
if (index < 0) {
throw new IllegalArgumentException("[" + currentPhaseName + "] is not a valid phase for lifecycle type [" + TYPE + "]");
} else {
// Find the previous phase before `index` that exists in `phases` and return it
while (--index >=0) {
String phaseName = VALID_PHASES.get(index);
if (phases.containsKey(phaseName)) {
return phaseName;
}
}
// if we have exhausted VALID_PHASES and haven't found a matching
// phase in `phases` return null indicating there is no previous phase
// available
return null;
}
}

public List<LifecycleAction> getOrderedActions(Phase phase) {
Map<String, LifecycleAction> actions = phase.getActions();
switch (phase.getName()) {
Expand All @@ -98,6 +140,45 @@ public List<LifecycleAction> getOrderedActions(Phase phase) {
throw new IllegalArgumentException("lifecycle type[" + TYPE + "] does not support phase[" + phase.getName() + "]");
}
}

@Override
public String getNextActionName(String currentActionName, Phase phase) {
List<String> orderedActionNames;
switch (phase.getName()) {
case "hot":
orderedActionNames = ORDERED_VALID_HOT_ACTIONS;
break;
case "warm":
orderedActionNames = ORDERED_VALID_WARM_ACTIONS;
break;
case "cold":
orderedActionNames = ORDERED_VALID_COLD_ACTIONS;
break;
case "delete":
orderedActionNames = ORDERED_VALID_DELETE_ACTIONS;
break;
default:
throw new IllegalArgumentException("lifecycle type[" + TYPE + "] does not support phase[" + phase.getName() + "]");
}

int index = orderedActionNames.indexOf(currentActionName);
if (index < 0) {
throw new IllegalArgumentException("[" + currentActionName + "] is not a valid action for phase [" + phase.getName()
+ "] in lifecycle type [" + TYPE + "]");
} else {
// Find the next action after `index` that exists in the phase and return it
while (++index < orderedActionNames.size()) {
String actionName = orderedActionNames.get(index);
if (phase.getActions().containsKey(actionName)) {
return actionName;
}
}
// if we have exhausted `validActions` and haven't found a matching
// action in the Phase return null indicating there is no next
// action available
return null;
}
}

@Override
public void validate(Collection<Phase> phases) {
Expand Down
Loading