Split RolloverStep into Wait and Action steps (#35524)

RolloverAction will now periodically check the rollover conditions using
the Rollover API with the dry_run option as an AsyncWaitStep, then run
the rollover itself by calling the Rollover API with no conditions,
which will always roll over, as an AsyncActionStep. This will resolve
race condition issues in policies using RolloverAction.
This commit is contained in:
Gordon Brown 2018-11-15 17:11:31 -07:00 committed by GitHub
parent ce35d049e9
commit 3883e9bf4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 530 additions and 239 deletions

View File

@ -194,7 +194,7 @@ public class IndexLifecycleIT extends ESRestHighLevelClientTestCase {
assertEquals("foo-01", fooResponse.getIndex());
assertEquals("hot", fooResponse.getPhase());
assertEquals("rollover", fooResponse.getAction());
assertEquals("attempt_rollover", fooResponse.getStep());
assertEquals("check-rollover-ready", fooResponse.getStep());
assertEquals(new PhaseExecutionInfo(policy.getName(), new Phase("", hotPhase.getMinimumAge(), hotPhase.getActions()),
1L, expectedPolicyModifiedDate), fooResponse.getPhaseExecutionInfo());
IndexLifecycleExplainResponse bazResponse = indexResponses.get("baz-01");
@ -203,7 +203,7 @@ public class IndexLifecycleIT extends ESRestHighLevelClientTestCase {
assertEquals("baz-01", bazResponse.getIndex());
assertEquals("hot", bazResponse.getPhase());
assertEquals("rollover", bazResponse.getAction());
assertEquals("attempt_rollover", bazResponse.getStep());
assertEquals("check-rollover-ready", bazResponse.getStep());
IndexLifecycleExplainResponse squashResponse = indexResponses.get("squash");
assertNotNull(squashResponse);
assertFalse(squashResponse.managedByILM());

View File

@ -177,7 +177,7 @@ PUT my_index
////
The <<ilm-explain-lifecycle,Explain API>> is useful to introspect managed indices to see which phase definition they are currently executing.
Using this API, we can find out that `my_index` is currently attempting to be rolled over.
Using this API, we can find out that `my_index` is currently checking if it is ready to be rolled over.
[source,js]
--------------------------------------------------
@ -199,7 +199,7 @@ GET my_index/_ilm/explain
"phase_time_millis": 1538475653317,
"action": "rollover",
"action_time_millis": 1538475653317,
"step": "attempt_rollover",
"step": "check-rollover-ready",
"step_time_millis": 1538475653317,
"phase_execution": {
"policy": "my_executing_policy",
@ -275,7 +275,7 @@ GET my_index/_ilm/explain
"phase_time_millis": 1538475653317,
"action": "rollover",
"action_time_millis": 1538475653317,
"step": "attempt_rollover",
"step": "check-rollover-ready",
"step_time_millis": 1538475653317,
"phase_execution": {
"policy": "my_executing_policy",

View File

@ -132,18 +132,23 @@ public class RolloverAction implements LifecycleAction {
@Override
public List<Step> toSteps(Client client, String phase, Step.StepKey nextStepKey) {
StepKey waitForRolloverReadyStepKey = new StepKey(phase, NAME, WaitForRolloverReadyStep.NAME);
StepKey rolloverStepKey = new StepKey(phase, NAME, RolloverStep.NAME);
StepKey updateDateStepKey = new StepKey(phase, NAME, UpdateRolloverLifecycleDateStep.NAME);
RolloverStep rolloverStep = new RolloverStep(new StepKey(phase, NAME, RolloverStep.NAME), updateDateStepKey, client,
maxSize, maxAge, maxDocs);
WaitForRolloverReadyStep waitForRolloverReadyStep = new WaitForRolloverReadyStep(waitForRolloverReadyStepKey, rolloverStepKey,
client, maxSize, maxAge, maxDocs);
RolloverStep rolloverStep = new RolloverStep(rolloverStepKey, updateDateStepKey, client);
UpdateRolloverLifecycleDateStep updateDateStep = new UpdateRolloverLifecycleDateStep(updateDateStepKey, nextStepKey);
return Arrays.asList(rolloverStep, updateDateStep);
return Arrays.asList(waitForRolloverReadyStep, rolloverStep, updateDateStep);
}
@Override
public List<StepKey> toStepKeys(String phase) {
StepKey rolloverReadyStepKey = new StepKey(phase, NAME, WaitForRolloverReadyStep.NAME);
StepKey rolloverStepKey = new StepKey(phase, NAME, RolloverStep.NAME);
StepKey updateDateStepKey = new StepKey(phase, NAME, UpdateRolloverLifecycleDateStep.NAME);
return Arrays.asList(rolloverStepKey, updateDateStepKey);
return Arrays.asList(rolloverReadyStepKey, rolloverStepKey, updateDateStepKey);
}
@Override

View File

@ -5,43 +5,28 @@
*/
package org.elasticsearch.xpack.core.indexlifecycle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ResourceAlreadyExistsException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
public class RolloverStep extends AsyncWaitStep {
/**
* Unconditionally rolls over an index using the Rollover API.
*/
public class RolloverStep extends AsyncActionStep {
public static final String NAME = "attempt_rollover";
private static final Logger logger = LogManager.getLogger(RolloverStep.class);
private ByteSizeValue maxSize;
private TimeValue maxAge;
private Long maxDocs;
public RolloverStep(StepKey key, StepKey nextStepKey, Client client, ByteSizeValue maxSize, TimeValue maxAge,
Long maxDocs) {
public RolloverStep(StepKey key, StepKey nextStepKey, Client client) {
super(key, nextStepKey, client);
this.maxSize = maxSize;
this.maxAge = maxAge;
this.maxDocs = maxDocs;
}
@Override
public void evaluateCondition(IndexMetaData indexMetaData, Listener listener) {
public void performAction(IndexMetaData indexMetaData, ClusterState currentClusterState, Listener listener) {
String rolloverAlias = RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING.get(indexMetaData.getSettings());
if (Strings.isNullOrEmpty(rolloverAlias)) {
@ -58,49 +43,19 @@ public class RolloverStep extends AsyncWaitStep {
return;
}
// Calling rollover with no conditions will always roll over the index
RolloverRequest rolloverRequest = new RolloverRequest(rolloverAlias, null);
if (maxAge != null) {
rolloverRequest.addMaxIndexAgeCondition(maxAge);
}
if (maxSize != null) {
rolloverRequest.addMaxIndexSizeCondition(maxSize);
}
if (maxDocs != null) {
rolloverRequest.addMaxIndexDocsCondition(maxDocs);
}
getClient().admin().indices().rolloverIndex(rolloverRequest,
ActionListener.wrap(response -> listener.onResponse(response.isRolledOver(), new EmptyInfo()), exception -> {
if (exception instanceof ResourceAlreadyExistsException) {
// This can happen sometimes when this step is executed multiple times
if (logger.isTraceEnabled()) {
logger.debug(() -> new ParameterizedMessage("{} index cannot roll over because the next index already exists, " +
"skipping to next step", indexMetaData.getIndex()), exception);
} else {
logger.debug("{} index cannot roll over because the next index already exists, skipping to next step",
indexMetaData.getIndex());
}
listener.onResponse(true, new EmptyInfo());
} else {
listener.onFailure(exception);
}
}));
ActionListener.wrap(response -> {
assert response.isRolledOver() : "the only way this rollover call should fail is with an exception";
listener.onResponse(response.isRolledOver());
}, listener::onFailure));
}
ByteSizeValue getMaxSize() {
return maxSize;
}
TimeValue getMaxAge() {
return maxAge;
}
Long getMaxDocs() {
return maxDocs;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), maxSize, maxAge, maxDocs);
return Objects.hash(super.hashCode());
}
@Override
@ -112,19 +67,6 @@ public class RolloverStep extends AsyncWaitStep {
return false;
}
RolloverStep other = (RolloverStep) obj;
return super.equals(obj) &&
Objects.equals(maxSize, other.maxSize) &&
Objects.equals(maxAge, other.maxAge) &&
Objects.equals(maxDocs, other.maxDocs);
}
// We currently have no information to provide for this AsyncWaitStep, so this is an empty object
private class EmptyInfo implements ToXContentObject {
private EmptyInfo() {}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder;
}
return super.equals(obj);
}
}

View File

@ -32,8 +32,8 @@ public class UpdateRolloverLifecycleDateStep extends ClusterStateActionStep {
}
RolloverInfo rolloverInfo = indexMetaData.getRolloverInfos().get(rolloverAlias);
if (rolloverInfo == null) {
throw new IllegalStateException("no rollover info found for [" + indexMetaData.getIndex().getName() + "], either the index " +
"has not yet rolled over or a subsequent index was created outside of Index Lifecycle Management");
throw new IllegalStateException("no rollover info found for [" + indexMetaData.getIndex().getName() + "] with alias [" +
rolloverAlias + "], the index has not yet rolled over with that alias");
}
LifecycleExecutionState.Builder newLifecycleState = LifecycleExecutionState

View File

@ -0,0 +1,118 @@
/*
* 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.action.ActionListener;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
/**
* Waits for at least one rollover condition to be satisfied, using the Rollover API's dry_run option.
*/
public class WaitForRolloverReadyStep extends AsyncWaitStep {
public static final String NAME = "check-rollover-ready";
private final ByteSizeValue maxSize;
private final TimeValue maxAge;
private final Long maxDocs;
public WaitForRolloverReadyStep(StepKey key, StepKey nextStepKey, Client client, ByteSizeValue maxSize, TimeValue maxAge,
Long maxDocs) {
super(key, nextStepKey, client);
this.maxSize = maxSize;
this.maxAge = maxAge;
this.maxDocs = maxDocs;
}
@Override
public void evaluateCondition(IndexMetaData indexMetaData, Listener listener) {
String rolloverAlias = RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING.get(indexMetaData.getSettings());
if (Strings.isNullOrEmpty(rolloverAlias)) {
listener.onFailure(new IllegalArgumentException(String.format(Locale.ROOT,
"setting [%s] for index [%s] is empty or not defined", RolloverAction.LIFECYCLE_ROLLOVER_ALIAS,
indexMetaData.getIndex().getName())));
return;
}
if (indexMetaData.getAliases().containsKey(rolloverAlias) == false) {
listener.onFailure(new IllegalArgumentException(String.format(Locale.ROOT,
"%s [%s] does not point to index [%s]", RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, rolloverAlias,
indexMetaData.getIndex().getName())));
return;
}
RolloverRequest rolloverRequest = new RolloverRequest(rolloverAlias, null);
rolloverRequest.dryRun(true);
if (maxAge != null) {
rolloverRequest.addMaxIndexAgeCondition(maxAge);
}
if (maxSize != null) {
rolloverRequest.addMaxIndexSizeCondition(maxSize);
}
if (maxDocs != null) {
rolloverRequest.addMaxIndexDocsCondition(maxDocs);
}
getClient().admin().indices().rolloverIndex(rolloverRequest,
ActionListener.wrap(response -> listener.onResponse(response.getConditionStatus().values().stream().anyMatch(i -> i),
new WaitForRolloverReadyStep.EmptyInfo()), listener::onFailure));
}
ByteSizeValue getMaxSize() {
return maxSize;
}
TimeValue getMaxAge() {
return maxAge;
}
Long getMaxDocs() {
return maxDocs;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), maxSize, maxAge, maxDocs);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
WaitForRolloverReadyStep other = (WaitForRolloverReadyStep) obj;
return super.equals(obj) &&
Objects.equals(maxSize, other.maxSize) &&
Objects.equals(maxAge, other.maxAge) &&
Objects.equals(maxDocs, other.maxDocs);
}
// We currently have no information to provide for this AsyncWaitStep, so this is an empty object
private class EmptyInfo implements ToXContentObject {
private EmptyInfo() {
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder;
}
}
}

View File

@ -80,17 +80,21 @@ public class RolloverActionTests extends AbstractActionTestCase<RolloverAction>
randomAlphaOfLengthBetween(1, 10));
List<Step> steps = action.toSteps(null, phase, nextStepKey);
assertNotNull(steps);
assertEquals(2, steps.size());
StepKey expectedFirstStepKey = new StepKey(phase, RolloverAction.NAME, RolloverStep.NAME);
StepKey expectedSecondStepKey = new StepKey(phase, RolloverAction.NAME, UpdateRolloverLifecycleDateStep.NAME);
RolloverStep firstStep = (RolloverStep) steps.get(0);
UpdateRolloverLifecycleDateStep secondStep = (UpdateRolloverLifecycleDateStep) steps.get(1);
assertEquals(3, steps.size());
StepKey expectedFirstStepKey = new StepKey(phase, RolloverAction.NAME, WaitForRolloverReadyStep.NAME);
StepKey expectedSecondStepKey = new StepKey(phase, RolloverAction.NAME, RolloverStep.NAME);
StepKey expectedThirdStepKey = new StepKey(phase, RolloverAction.NAME, UpdateRolloverLifecycleDateStep.NAME);
WaitForRolloverReadyStep firstStep = (WaitForRolloverReadyStep) steps.get(0);
RolloverStep secondStep = (RolloverStep) steps.get(1);
UpdateRolloverLifecycleDateStep thirdStep = (UpdateRolloverLifecycleDateStep) steps.get(2);
assertEquals(expectedFirstStepKey, firstStep.getKey());
assertEquals(expectedSecondStepKey, secondStep.getKey());
assertEquals(expectedThirdStepKey, thirdStep.getKey());
assertEquals(secondStep.getKey(), firstStep.getNextStepKey());
assertEquals(thirdStep.getKey(), secondStep.getNextStepKey());
assertEquals(action.getMaxSize(), firstStep.getMaxSize());
assertEquals(action.getMaxAge(), firstStep.getMaxAge());
assertEquals(action.getMaxDocs(), firstStep.getMaxDocs());
assertEquals(nextStepKey, secondStep.getNextStepKey());
assertEquals(nextStepKey, thirdStep.getNextStepKey());
}
}

View File

@ -8,10 +8,6 @@ package org.elasticsearch.xpack.core.indexlifecycle;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.rollover.Condition;
import org.elasticsearch.action.admin.indices.rollover.MaxAgeCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
import org.elasticsearch.client.AdminClient;
@ -19,10 +15,6 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;
import org.junit.Before;
import org.mockito.Mockito;
@ -30,10 +22,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
@ -50,65 +39,42 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
public RolloverStep createRandomInstance() {
StepKey stepKey = randomStepKey();
StepKey nextStepKey = randomStepKey();
ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();
TimeValue maxAge = (maxDocs == null && maxSize == null || randomBoolean())
? TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test")
: null;
return new RolloverStep(stepKey, nextStepKey, client, maxSize, maxAge, maxDocs);
return new RolloverStep(stepKey, nextStepKey, client);
}
@Override
public RolloverStep mutateInstance(RolloverStep instance) {
StepKey key = instance.getKey();
StepKey nextKey = instance.getNextStepKey();
ByteSizeValue maxSize = instance.getMaxSize();
TimeValue maxAge = instance.getMaxAge();
Long maxDocs = instance.getMaxDocs();
switch (between(0, 4)) {
switch (between(0, 1)) {
case 0:
key = new StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5));
break;
case 1:
nextKey = new StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5));
break;
case 2:
maxSize = randomValueOtherThan(maxSize, () -> {
ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
return new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
});
break;
case 3:
maxAge = TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test");
break;
case 4:
maxDocs = randomNonNegativeLong();
break;
default:
throw new AssertionError("Illegal randomisation branch");
}
return new RolloverStep(key, nextKey, instance.getClient(), maxSize, maxAge, maxDocs);
return new RolloverStep(key, nextKey, instance.getClient());
}
@Override
public RolloverStep copyInstance(RolloverStep instance) {
return new RolloverStep(instance.getKey(), instance.getNextStepKey(), instance.getClient(),
instance.getMaxSize(), instance.getMaxAge(), instance.getMaxDocs());
return new RolloverStep(instance.getKey(), instance.getNextStepKey(), instance.getClient());
}
private static void assertRolloverIndexRequest(RolloverRequest request, String alias, Set<Condition<?>> expectedConditions) {
private static void assertRolloverIndexRequest(RolloverRequest request, String alias) {
assertNotNull(request);
assertEquals(1, request.indices().length);
assertEquals(alias, request.indices()[0]);
assertEquals(alias, request.getAlias());
assertEquals(expectedConditions.size(), request.getConditions().size());
Set<Object> expectedConditionValues = expectedConditions.stream().map(Condition::value).collect(Collectors.toSet());
Set<Object> actualConditionValues = request.getConditions().values().stream()
.map(Condition::value).collect(Collectors.toSet());
assertEquals(expectedConditionValues, actualConditionValues);
assertFalse(request.isDryRun());
assertEquals(0, request.getConditions().size());
}
public void testPerformAction() {
@ -132,17 +98,7 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
assertRolloverIndexRequest(request, alias);
listener.onResponse(new RolloverResponse(null, null, Collections.emptyMap(), request.isDryRun(), true, true, true));
return null;
}
@ -150,10 +106,10 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> actionCompleted = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
step.performAction(indexMetaData, null, new AsyncActionStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject obj) {
public void onResponse(boolean complete) {
actionCompleted.set(complete);
}
@ -170,64 +126,6 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
Mockito.verify(indicesClient, Mockito.only()).rolloverIndex(Mockito.any(), Mockito.any());
}
public void testPerformActionNotComplete() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.putAlias(AliasMetaData.builder(alias))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
RolloverStep step = createRandomInstance();
AdminClient adminClient = Mockito.mock(AdminClient.class);
IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class);
Mockito.when(client.admin()).thenReturn(adminClient);
Mockito.when(adminClient.indices()).thenReturn(indicesClient);
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
listener.onResponse(new RolloverResponse(null, null, Collections.emptyMap(), request.isDryRun(), false, true, true));
return null;
}
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> actionCompleted = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject obj) {
actionCompleted.set(complete);
}
@Override
public void onFailure(Exception e) {
throw new AssertionError("Unexpected method call", e);
}
});
assertEquals(false, actionCompleted.get());
Mockito.verify(client, Mockito.only()).admin();
Mockito.verify(adminClient, Mockito.only()).indices();
Mockito.verify(indicesClient, Mockito.only()).rolloverIndex(Mockito.any(), Mockito.any());
}
public void testPerformActionFailure() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
@ -249,17 +147,7 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
assertRolloverIndexRequest(request, alias);
listener.onFailure(exception);
return null;
}
@ -267,10 +155,10 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
step.performAction(indexMetaData, null, new AsyncActionStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject obj) {
public void onResponse(boolean complete) {
throw new AssertionError("Unexpected method call");
}
@ -296,9 +184,9 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
RolloverStep step = createRandomInstance();
SetOnce<Exception> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
step.performAction(indexMetaData, null, new AsyncActionStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject obj) {
public void onResponse(boolean complete) {
throw new AssertionError("Unexpected method call");
}
@ -321,9 +209,9 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
RolloverStep step = createRandomInstance();
SetOnce<Exception> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
step.performAction(indexMetaData, null, new AsyncActionStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject obj) {
public void onResponse(boolean complete) {
throw new AssertionError("Unexpected method call");
}
@ -336,6 +224,5 @@ public class RolloverStepTests extends AbstractStepTestCase<RolloverStep> {
assertThat(exceptionThrown.get().getMessage(), equalTo(String.format(Locale.ROOT,
"%s [%s] does not point to index [%s]", RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias,
indexMetaData.getIndex().getName())));
}
}

View File

@ -87,8 +87,8 @@ public class UpdateRolloverLifecycleDateStepTests extends AbstractStepTestCase<U
IllegalStateException exceptionThrown = expectThrows(IllegalStateException.class,
() -> step.performAction(indexMetaData.getIndex(), clusterState));
assertThat(exceptionThrown.getMessage(),
equalTo("no rollover info found for [" + indexMetaData.getIndex().getName() + "], either the index " +
"has not yet rolled over or a subsequent index was created outside of Index Lifecycle Management"));
equalTo("no rollover info found for [" + indexMetaData.getIndex().getName() + "] with alias [" + alias + "], the index " +
"has not yet rolled over with that alias"));
}
public void testPerformActionWithNoRolloverAliasSetting() {

View File

@ -0,0 +1,345 @@
/*
* 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.apache.lucene.util.SetOnce;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.rollover.Condition;
import org.elasticsearch.action.admin.indices.rollover.MaxAgeCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition;
import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.junit.Before;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
public class WaitForRolloverReadyStepTests extends AbstractStepTestCase<WaitForRolloverReadyStep> {
private Client client;
@Before
public void setup() {
client = Mockito.mock(Client.class);
}
@Override
protected WaitForRolloverReadyStep createRandomInstance() {
Step.StepKey stepKey = randomStepKey();
Step.StepKey nextStepKey = randomStepKey();
ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
ByteSizeValue maxSize = randomBoolean() ? null : new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
Long maxDocs = randomBoolean() ? null : randomNonNegativeLong();
TimeValue maxAge = (maxDocs == null && maxSize == null || randomBoolean())
? TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test")
: null;
return new WaitForRolloverReadyStep(stepKey, nextStepKey, client, maxSize, maxAge, maxDocs);
}
@Override
protected WaitForRolloverReadyStep mutateInstance(WaitForRolloverReadyStep instance) {
Step.StepKey key = instance.getKey();
Step.StepKey nextKey = instance.getNextStepKey();
ByteSizeValue maxSize = instance.getMaxSize();
TimeValue maxAge = instance.getMaxAge();
Long maxDocs = instance.getMaxDocs();
switch (between(0, 4)) {
case 0:
key = new Step.StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5));
break;
case 1:
nextKey = new Step.StepKey(key.getPhase(), key.getAction(), key.getName() + randomAlphaOfLength(5));
break;
case 2:
maxSize = randomValueOtherThan(maxSize, () -> {
ByteSizeUnit maxSizeUnit = randomFrom(ByteSizeUnit.values());
return new ByteSizeValue(randomNonNegativeLong() / maxSizeUnit.toBytes(1), maxSizeUnit);
});
break;
case 3:
maxAge = TimeValue.parseTimeValue(randomPositiveTimeValue(), "rollover_action_test");
break;
case 4:
maxDocs = randomNonNegativeLong();
break;
default:
throw new AssertionError("Illegal randomisation branch");
}
return new WaitForRolloverReadyStep(key, nextKey, instance.getClient(), maxSize, maxAge, maxDocs);
}
@Override
protected WaitForRolloverReadyStep copyInstance(WaitForRolloverReadyStep instance) {
return new WaitForRolloverReadyStep(instance.getKey(), instance.getNextStepKey(), instance.getClient(),
instance.getMaxSize(), instance.getMaxAge(), instance.getMaxDocs());
}
private static void assertRolloverIndexRequest(RolloverRequest request, String alias, Set<Condition<?>> expectedConditions) {
assertNotNull(request);
assertEquals(1, request.indices().length);
assertEquals(alias, request.indices()[0]);
assertEquals(alias, request.getAlias());
assertEquals(expectedConditions.size(), request.getConditions().size());
assertTrue(request.isDryRun());
Set<Object> expectedConditionValues = expectedConditions.stream().map(Condition::value).collect(Collectors.toSet());
Set<Object> actualConditionValues = request.getConditions().values().stream()
.map(Condition::value).collect(Collectors.toSet());
assertEquals(expectedConditionValues, actualConditionValues);
}
public void testEvaluateCondition() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.putAlias(AliasMetaData.builder(alias))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
WaitForRolloverReadyStep step = createRandomInstance();
AdminClient adminClient = Mockito.mock(AdminClient.class);
IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class);
Mockito.when(client.admin()).thenReturn(adminClient);
Mockito.when(adminClient.indices()).thenReturn(indicesClient);
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
Map<String, Boolean> conditionResults = expectedConditions.stream()
.collect(Collectors.toMap(Condition::toString, condition -> true));
listener.onResponse(new RolloverResponse(null, null, conditionResults, request.isDryRun(), false, false, false));
return null;
}
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> conditionsMet = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject infomationContext) {
conditionsMet.set(complete);
}
@Override
public void onFailure(Exception e) {
throw new AssertionError("Unexpected method call", e);
}
});
assertEquals(true, conditionsMet.get());
Mockito.verify(client, Mockito.only()).admin();
Mockito.verify(adminClient, Mockito.only()).indices();
Mockito.verify(indicesClient, Mockito.only()).rolloverIndex(Mockito.any(), Mockito.any());
}
public void testPerformActionNotComplete() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.putAlias(AliasMetaData.builder(alias))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
WaitForRolloverReadyStep step = createRandomInstance();
AdminClient adminClient = Mockito.mock(AdminClient.class);
IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class);
Mockito.when(client.admin()).thenReturn(adminClient);
Mockito.when(adminClient.indices()).thenReturn(indicesClient);
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
Map<String, Boolean> conditionResults = expectedConditions.stream()
.collect(Collectors.toMap(Condition::toString, condition -> false));
listener.onResponse(new RolloverResponse(null, null, conditionResults, request.isDryRun(), false, false, false));
return null;
}
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> actionCompleted = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject infomationContext) {
actionCompleted.set(complete);
}
@Override
public void onFailure(Exception e) {
throw new AssertionError("Unexpected method call", e);
}
});
assertEquals(false, actionCompleted.get());
Mockito.verify(client, Mockito.only()).admin();
Mockito.verify(adminClient, Mockito.only()).indices();
Mockito.verify(indicesClient, Mockito.only()).rolloverIndex(Mockito.any(), Mockito.any());
}
public void testPerformActionFailure() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.putAlias(AliasMetaData.builder(alias))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
Exception exception = new RuntimeException();
WaitForRolloverReadyStep step = createRandomInstance();
AdminClient adminClient = Mockito.mock(AdminClient.class);
IndicesAdminClient indicesClient = Mockito.mock(IndicesAdminClient.class);
Mockito.when(client.admin()).thenReturn(adminClient);
Mockito.when(adminClient.indices()).thenReturn(indicesClient);
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
RolloverRequest request = (RolloverRequest) invocation.getArguments()[0];
@SuppressWarnings("unchecked")
ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArguments()[1];
Set<Condition<?>> expectedConditions = new HashSet<>();
if (step.getMaxAge() != null) {
expectedConditions.add(new MaxAgeCondition(step.getMaxAge()));
}
if (step.getMaxSize() != null) {
expectedConditions.add(new MaxSizeCondition(step.getMaxSize()));
}
if (step.getMaxDocs() != null) {
expectedConditions.add(new MaxDocsCondition(step.getMaxDocs()));
}
assertRolloverIndexRequest(request, alias, expectedConditions);
listener.onFailure(exception);
return null;
}
}).when(indicesClient).rolloverIndex(Mockito.any(), Mockito.any());
SetOnce<Boolean> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject infomationContext) {
throw new AssertionError("Unexpected method call");
}
@Override
public void onFailure(Exception e) {
assertSame(exception, e);
exceptionThrown.set(true);
}
});
assertEquals(true, exceptionThrown.get());
Mockito.verify(client, Mockito.only()).admin();
Mockito.verify(adminClient, Mockito.only()).indices();
Mockito.verify(indicesClient, Mockito.only()).rolloverIndex(Mockito.any(), Mockito.any());
}
public void testPerformActionInvalidNullOrEmptyAlias() {
String alias = randomBoolean() ? "" : null;
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
WaitForRolloverReadyStep step = createRandomInstance();
SetOnce<Exception> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject infomationContext) {
throw new AssertionError("Unexpected method call");
}
@Override
public void onFailure(Exception e) {
exceptionThrown.set(e);
}
});
assertThat(exceptionThrown.get().getClass(), equalTo(IllegalArgumentException.class));
assertThat(exceptionThrown.get().getMessage(), equalTo(String.format(Locale.ROOT,
"setting [%s] for index [%s] is empty or not defined", RolloverAction.LIFECYCLE_ROLLOVER_ALIAS,
indexMetaData.getIndex().getName())));
}
public void testPerformActionAliasDoesNotPointToIndex() {
String alias = randomAlphaOfLength(5);
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
.settings(settings(Version.CURRENT).put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias))
.numberOfShards(randomIntBetween(1, 5)).numberOfReplicas(randomIntBetween(0, 5)).build();
WaitForRolloverReadyStep step = createRandomInstance();
SetOnce<Exception> exceptionThrown = new SetOnce<>();
step.evaluateCondition(indexMetaData, new AsyncWaitStep.Listener() {
@Override
public void onResponse(boolean complete, ToXContentObject infomationContext) {
throw new AssertionError("Unexpected method call");
}
@Override
public void onFailure(Exception e) {
exceptionThrown.set(e);
}
});
assertThat(exceptionThrown.get().getClass(), equalTo(IllegalArgumentException.class));
assertThat(exceptionThrown.get().getMessage(), equalTo(String.format(Locale.ROOT,
"%s [%s] does not point to index [%s]", RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, alias,
indexMetaData.getIndex().getName())));
}
}

View File

@ -21,9 +21,9 @@ import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings;
import org.elasticsearch.xpack.core.indexlifecycle.Phase;
import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction;
import org.elasticsearch.xpack.core.indexlifecycle.RolloverStep;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;
import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep;
import org.elasticsearch.xpack.core.indexlifecycle.WaitForRolloverReadyStep;
import java.io.IOException;
import java.util.HashMap;
@ -52,7 +52,6 @@ public class ChangePolicyforIndexIT extends ESRestTestCase {
* settings from the second policy are set ont he index (proving the second
* policy was used for the warm phase)
*/
@AwaitsFix(bugUrl="https://github.com/elastic/elasticsearch/issues/35244")
public void testChangePolicyForIndex() throws Exception {
String indexName = "test-000001";
// create policy_1 and policy_2
@ -92,7 +91,7 @@ public class ChangePolicyforIndexIT extends ESRestTestCase {
ensureGreen(indexName);
// Check the index is on the attempt rollover step
assertBusy(() -> assertStep(indexName, new StepKey("hot", RolloverAction.NAME, RolloverStep.NAME)));
assertBusy(() -> assertStep(indexName, new StepKey("hot", RolloverAction.NAME, WaitForRolloverReadyStep.NAME)));
// Change the policy to policy_2
Request changePolicyRequest = new Request("PUT", "/" + indexName + "/_settings");
@ -102,7 +101,7 @@ public class ChangePolicyforIndexIT extends ESRestTestCase {
assertOK(client().performRequest(changePolicyRequest));
// Check the index is still on the attempt rollover step
assertBusy(() -> assertStep(indexName, new StepKey("hot", RolloverAction.NAME, RolloverStep.NAME)));
assertBusy(() -> assertStep(indexName, new StepKey("hot", RolloverAction.NAME, WaitForRolloverReadyStep.NAME)));
// Index a single document
XContentBuilder document = jsonBuilder().startObject();
@ -131,11 +130,6 @@ public class ChangePolicyforIndexIT extends ESRestTestCase {
assertEquals("node-1,node-2", includesAllocation);
}
public void testTempAwaitFix() {
// this is a test stub since there is only one test in this class and it is
// awaits-fixed. This test is to be removed once testChangePolicyForIndex is resolved
}
private void assertStep(String indexName, StepKey expectedStep) throws IOException {
Response explainResponse = client().performRequest(new Request("GET", "/" + indexName + "/_ilm/explain"));
assertOK(explainResponse);

View File

@ -33,6 +33,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction;
import org.elasticsearch.xpack.core.indexlifecycle.ShrinkStep;
import org.elasticsearch.xpack.core.indexlifecycle.Step.StepKey;
import org.elasticsearch.xpack.core.indexlifecycle.TerminalPolicyStep;
import org.elasticsearch.xpack.core.indexlifecycle.WaitForRolloverReadyStep;
import org.junit.Before;
import java.io.IOException;
@ -237,12 +238,10 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
createIndexWithSettings(originalIndex, Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
.put(RolloverAction.LIFECYCLE_ROLLOVER_ALIAS, "alias"));
// create policy
createNewSingletonPolicy("hot", new RolloverAction(null, null, 1L));
// update policy on index
updatePolicy(originalIndex, policy);
// Manually create the new index
Request request = new Request("PUT", "/" + secondIndex);
request.setJsonEntity("{\n \"settings\": " + Strings.toString(Settings.builder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
@ -250,17 +249,14 @@ public class TimeSeriesLifecycleActionsIT extends ESRestTestCase {
client().performRequest(request);
// wait for the shards to initialize
ensureGreen(secondIndex);
// index another doc to trigger the policy
index(client(), originalIndex, "_id", "foo", "bar");
assertBusy(() -> {
logger.info(originalIndex + ": " + getStepKeyForIndex(originalIndex));
logger.info(secondIndex + ": " + getStepKeyForIndex(secondIndex));
assertThat(getStepKeyForIndex(originalIndex), equalTo(new StepKey("hot", RolloverAction.NAME, ErrorStep.NAME)));
assertThat(getFailedStepForIndex(originalIndex), equalTo("update-rollover-lifecycle-date"));
assertThat(getReasonForIndex(originalIndex), equalTo("no rollover info found for [" + originalIndex + "], either the index " +
"has not yet rolled over or a subsequent index was created outside of Index Lifecycle Management"));
assertThat(getFailedStepForIndex(originalIndex), equalTo(WaitForRolloverReadyStep.NAME));
assertThat(getReasonForIndex(originalIndex), containsString("already exists"));
});
}