#101193 Preserve Step Info Across ILM Auto Retries (#113187)

* Add new Previous Step Info field to LifecycleExecutionState

* Add new field to IndexLifecycleExplainResponse

* Add new field to TransportExplainLifecycleAction

* Add logic to IndexLifecycleTransition to keep previous setp info

* Switch tests to use Java standard Clock class

for any time based testing, this is the recommended method

* Fix tests for new field

Also refactor tests to newer style

* Add test to ensure step info is preserved

Across auto retries

* Add docs for new field

* Changelog Entry

* Update docs/changelog/113187.yaml

* Revert "Switch tests to use Java standard Clock class"

This reverts commit 241074c735.

* PR Changes

* PR Changes - Improve docs wording

Co-authored-by: Mary Gouseti <mgouseti@gmail.com>

* Integration test for new ILM explain field

* Use ROOT locale instead of default toLowerCase

* PR Changes - Switch to block strings

* Remove forbidden API usage

---------

Co-authored-by: Mary Gouseti <mgouseti@gmail.com>
This commit is contained in:
Luke Whiting 2024-09-30 11:44:46 +01:00 committed by GitHub
parent 07846d43d2
commit b1b249d26b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 210 additions and 113 deletions

View file

@ -0,0 +1,5 @@
pr: 113187
summary: Preserve Step Info Across ILM Auto Retries
area: ILM+SLM
type: enhancement
issues: []

View file

@ -303,6 +303,12 @@ the case.
"index_uuid": "H7lF9n36Rzqa-KfKcnGQMg", "index_uuid": "H7lF9n36Rzqa-KfKcnGQMg",
"index": "test-000057" "index": "test-000057"
}, },
"previous_step_info": { <5>
"type": "cluster_block_exception",
"reason": "index [test-000057/H7lF9n36Rzqa-KfKcnGQMg] blocked by: [FORBIDDEN/5/index read-only (api)",
"index_uuid": "H7lF9n36Rzqa-KfKcnGQMg",
"index": "test-000057"
},
"phase_execution": { "phase_execution": {
"policy": "my_lifecycle3", "policy": "my_lifecycle3",
"phase_definition": { "phase_definition": {
@ -329,3 +335,4 @@ is true, {ilm-init} will retry the failed step automatically.
<3> Shows the number of attempted automatic retries to execute the failed <3> Shows the number of attempted automatic retries to execute the failed
step. step.
<4> What went wrong <4> What went wrong
<5> Contains a copy of the `step_info` field (when it exists) of the last attempted or executed step for diagnostic purposes, since the `step_info` is overwritten during each new attempt.

View file

@ -226,6 +226,7 @@ public class TransportVersions {
public static final TransportVersion SEMANTIC_TEXT_SEARCH_INFERENCE_ID = def(8_750_00_0); public static final TransportVersion SEMANTIC_TEXT_SEARCH_INFERENCE_ID = def(8_750_00_0);
public static final TransportVersion ML_INFERENCE_CHUNKING_SETTINGS = def(8_751_00_0); public static final TransportVersion ML_INFERENCE_CHUNKING_SETTINGS = def(8_751_00_0);
public static final TransportVersion SEMANTIC_QUERY_INNER_HITS = def(8_752_00_0); public static final TransportVersion SEMANTIC_QUERY_INNER_HITS = def(8_752_00_0);
public static final TransportVersion RETAIN_ILM_STEP_INFO = def(8_753_00_0);
/* /*
* STOP! READ THIS FIRST! No, really, * STOP! READ THIS FIRST! No, really,

View file

@ -28,6 +28,7 @@ public record LifecycleExecutionState(
Boolean isAutoRetryableError, Boolean isAutoRetryableError,
Integer failedStepRetryCount, Integer failedStepRetryCount,
String stepInfo, String stepInfo,
String previousStepInfo,
String phaseDefinition, String phaseDefinition,
Long lifecycleDate, Long lifecycleDate,
Long phaseTime, Long phaseTime,
@ -53,6 +54,7 @@ public record LifecycleExecutionState(
private static final String IS_AUTO_RETRYABLE_ERROR = "is_auto_retryable_error"; private static final String IS_AUTO_RETRYABLE_ERROR = "is_auto_retryable_error";
private static final String FAILED_STEP_RETRY_COUNT = "failed_step_retry_count"; private static final String FAILED_STEP_RETRY_COUNT = "failed_step_retry_count";
private static final String STEP_INFO = "step_info"; private static final String STEP_INFO = "step_info";
private static final String PREVIOUS_STEP_INFO = "previous_step_info";
private static final String PHASE_DEFINITION = "phase_definition"; private static final String PHASE_DEFINITION = "phase_definition";
private static final String SNAPSHOT_NAME = "snapshot_name"; private static final String SNAPSHOT_NAME = "snapshot_name";
private static final String SNAPSHOT_REPOSITORY = "snapshot_repository"; private static final String SNAPSHOT_REPOSITORY = "snapshot_repository";
@ -74,6 +76,7 @@ public record LifecycleExecutionState(
.setIsAutoRetryableError(state.isAutoRetryableError) .setIsAutoRetryableError(state.isAutoRetryableError)
.setFailedStepRetryCount(state.failedStepRetryCount) .setFailedStepRetryCount(state.failedStepRetryCount)
.setStepInfo(state.stepInfo) .setStepInfo(state.stepInfo)
.setPreviousStepInfo(state.previousStepInfo)
.setPhaseDefinition(state.phaseDefinition) .setPhaseDefinition(state.phaseDefinition)
.setIndexCreationDate(state.lifecycleDate) .setIndexCreationDate(state.lifecycleDate)
.setPhaseTime(state.phaseTime) .setPhaseTime(state.phaseTime)
@ -116,6 +119,10 @@ public record LifecycleExecutionState(
if (stepInfo != null) { if (stepInfo != null) {
builder.setStepInfo(stepInfo); builder.setStepInfo(stepInfo);
} }
String previousStepInfo = customData.get(PREVIOUS_STEP_INFO);
if (previousStepInfo != null) {
builder.setPreviousStepInfo(previousStepInfo);
}
String phaseDefinition = customData.get(PHASE_DEFINITION); String phaseDefinition = customData.get(PHASE_DEFINITION);
if (phaseDefinition != null) { if (phaseDefinition != null) {
builder.setPhaseDefinition(phaseDefinition); builder.setPhaseDefinition(phaseDefinition);
@ -224,6 +231,9 @@ public record LifecycleExecutionState(
if (stepInfo != null) { if (stepInfo != null) {
result.put(STEP_INFO, stepInfo); result.put(STEP_INFO, stepInfo);
} }
if (previousStepInfo != null) {
result.put(PREVIOUS_STEP_INFO, previousStepInfo);
}
if (lifecycleDate != null) { if (lifecycleDate != null) {
result.put(INDEX_CREATION_DATE, String.valueOf(lifecycleDate)); result.put(INDEX_CREATION_DATE, String.valueOf(lifecycleDate));
} }
@ -263,6 +273,7 @@ public record LifecycleExecutionState(
private String step; private String step;
private String failedStep; private String failedStep;
private String stepInfo; private String stepInfo;
private String previousStepInfo;
private String phaseDefinition; private String phaseDefinition;
private Long indexCreationDate; private Long indexCreationDate;
private Long phaseTime; private Long phaseTime;
@ -301,6 +312,11 @@ public record LifecycleExecutionState(
return this; return this;
} }
public Builder setPreviousStepInfo(String previousStepInfo) {
this.previousStepInfo = previousStepInfo;
return this;
}
public Builder setPhaseDefinition(String phaseDefinition) { public Builder setPhaseDefinition(String phaseDefinition) {
this.phaseDefinition = phaseDefinition; this.phaseDefinition = phaseDefinition;
return this; return this;
@ -370,6 +386,7 @@ public record LifecycleExecutionState(
isAutoRetryableError, isAutoRetryableError,
failedStepRetryCount, failedStepRetryCount,
stepInfo, stepInfo,
previousStepInfo,
phaseDefinition, phaseDefinition,
indexCreationDate, indexCreationDate,
phaseTime, phaseTime,

View file

@ -48,6 +48,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
private static final ParseField STEP_TIME_MILLIS_FIELD = new ParseField("step_time_millis"); private static final ParseField STEP_TIME_MILLIS_FIELD = new ParseField("step_time_millis");
private static final ParseField STEP_TIME_FIELD = new ParseField("step_time"); private static final ParseField STEP_TIME_FIELD = new ParseField("step_time");
private static final ParseField STEP_INFO_FIELD = new ParseField("step_info"); private static final ParseField STEP_INFO_FIELD = new ParseField("step_info");
private static final ParseField PREVIOUS_STEP_INFO_FIELD = new ParseField("previous_step_info");
private static final ParseField PHASE_EXECUTION_INFO = new ParseField("phase_execution"); private static final ParseField PHASE_EXECUTION_INFO = new ParseField("phase_execution");
private static final ParseField AGE_FIELD = new ParseField("age"); private static final ParseField AGE_FIELD = new ParseField("age");
private static final ParseField TIME_SINCE_INDEX_CREATION_FIELD = new ParseField("time_since_index_creation"); private static final ParseField TIME_SINCE_INDEX_CREATION_FIELD = new ParseField("time_since_index_creation");
@ -76,6 +77,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
(String) a[17], (String) a[17],
(String) a[18], (String) a[18],
(BytesReference) a[11], (BytesReference) a[11],
(BytesReference) a[21],
(PhaseExecutionInfo) a[12] (PhaseExecutionInfo) a[12]
// a[13] == "age" // a[13] == "age"
// a[20] == "time_since_index_creation" // a[20] == "time_since_index_creation"
@ -111,6 +113,11 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), SHRINK_INDEX_NAME); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), SHRINK_INDEX_NAME);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), INDEX_CREATION_DATE_MILLIS_FIELD); PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), INDEX_CREATION_DATE_MILLIS_FIELD);
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), TIME_SINCE_INDEX_CREATION_FIELD); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), TIME_SINCE_INDEX_CREATION_FIELD);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
XContentBuilder builder = JsonXContent.contentBuilder();
builder.copyCurrentStructure(p);
return BytesReference.bytes(builder);
}, PREVIOUS_STEP_INFO_FIELD);
} }
private final String index; private final String index;
@ -126,6 +133,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
private final Long stepTime; private final Long stepTime;
private final boolean managedByILM; private final boolean managedByILM;
private final BytesReference stepInfo; private final BytesReference stepInfo;
private final BytesReference previousStepInfo;
private final PhaseExecutionInfo phaseExecutionInfo; private final PhaseExecutionInfo phaseExecutionInfo;
private final Boolean isAutoRetryableError; private final Boolean isAutoRetryableError;
private final Integer failedStepRetryCount; private final Integer failedStepRetryCount;
@ -153,6 +161,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
String snapshotName, String snapshotName,
String shrinkIndexName, String shrinkIndexName,
BytesReference stepInfo, BytesReference stepInfo,
BytesReference previousStepInfo,
PhaseExecutionInfo phaseExecutionInfo PhaseExecutionInfo phaseExecutionInfo
) { ) {
return new IndexLifecycleExplainResponse( return new IndexLifecycleExplainResponse(
@ -174,6 +183,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
snapshotName, snapshotName,
shrinkIndexName, shrinkIndexName,
stepInfo, stepInfo,
previousStepInfo,
phaseExecutionInfo phaseExecutionInfo
); );
} }
@ -198,6 +208,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
null, null,
null, null,
null, null,
null,
null null
); );
} }
@ -221,6 +232,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
String snapshotName, String snapshotName,
String shrinkIndexName, String shrinkIndexName,
BytesReference stepInfo, BytesReference stepInfo,
BytesReference previousStepInfo,
PhaseExecutionInfo phaseExecutionInfo PhaseExecutionInfo phaseExecutionInfo
) { ) {
if (managedByILM) { if (managedByILM) {
@ -262,6 +274,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
|| actionTime != null || actionTime != null
|| stepTime != null || stepTime != null
|| stepInfo != null || stepInfo != null
|| previousStepInfo != null
|| phaseExecutionInfo != null) { || phaseExecutionInfo != null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Unmanaged index response must only contain fields: [" + MANAGED_BY_ILM_FIELD + ", " + INDEX_FIELD + "]" "Unmanaged index response must only contain fields: [" + MANAGED_BY_ILM_FIELD + ", " + INDEX_FIELD + "]"
@ -283,6 +296,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
this.isAutoRetryableError = isAutoRetryableError; this.isAutoRetryableError = isAutoRetryableError;
this.failedStepRetryCount = failedStepRetryCount; this.failedStepRetryCount = failedStepRetryCount;
this.stepInfo = stepInfo; this.stepInfo = stepInfo;
this.previousStepInfo = previousStepInfo;
this.phaseExecutionInfo = phaseExecutionInfo; this.phaseExecutionInfo = phaseExecutionInfo;
this.repositoryName = repositoryName; this.repositoryName = repositoryName;
this.snapshotName = snapshotName; this.snapshotName = snapshotName;
@ -314,6 +328,11 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
} else { } else {
indexCreationDate = null; indexCreationDate = null;
} }
if (in.getTransportVersion().onOrAfter(TransportVersions.RETAIN_ILM_STEP_INFO)) {
previousStepInfo = in.readOptionalBytesReference();
} else {
previousStepInfo = null;
}
} else { } else {
policyName = null; policyName = null;
lifecycleDate = null; lifecycleDate = null;
@ -327,6 +346,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
actionTime = null; actionTime = null;
stepTime = null; stepTime = null;
stepInfo = null; stepInfo = null;
previousStepInfo = null;
phaseExecutionInfo = null; phaseExecutionInfo = null;
repositoryName = null; repositoryName = null;
snapshotName = null; snapshotName = null;
@ -359,6 +379,9 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_1_0)) { if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_1_0)) {
out.writeOptionalLong(indexCreationDate); out.writeOptionalLong(indexCreationDate);
} }
if (out.getTransportVersion().onOrAfter(TransportVersions.RETAIN_ILM_STEP_INFO)) {
out.writeOptionalBytesReference(previousStepInfo);
}
} }
} }
@ -422,6 +445,10 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
return stepInfo; return stepInfo;
} }
public BytesReference getPreviousStepInfo() {
return previousStepInfo;
}
public PhaseExecutionInfo getPhaseExecutionInfo() { public PhaseExecutionInfo getPhaseExecutionInfo() {
return phaseExecutionInfo; return phaseExecutionInfo;
} }
@ -515,6 +542,9 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
if (stepInfo != null && stepInfo.length() > 0) { if (stepInfo != null && stepInfo.length() > 0) {
builder.rawField(STEP_INFO_FIELD.getPreferredName(), stepInfo.streamInput(), XContentType.JSON); builder.rawField(STEP_INFO_FIELD.getPreferredName(), stepInfo.streamInput(), XContentType.JSON);
} }
if (previousStepInfo != null && previousStepInfo.length() > 0) {
builder.rawField(PREVIOUS_STEP_INFO_FIELD.getPreferredName(), previousStepInfo.streamInput(), XContentType.JSON);
}
if (phaseExecutionInfo != null) { if (phaseExecutionInfo != null) {
builder.field(PHASE_EXECUTION_INFO.getPreferredName(), phaseExecutionInfo); builder.field(PHASE_EXECUTION_INFO.getPreferredName(), phaseExecutionInfo);
} }
@ -544,6 +574,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
snapshotName, snapshotName,
shrinkIndexName, shrinkIndexName,
stepInfo, stepInfo,
previousStepInfo,
phaseExecutionInfo phaseExecutionInfo
); );
} }
@ -575,6 +606,7 @@ public class IndexLifecycleExplainResponse implements ToXContentObject, Writeabl
&& Objects.equals(snapshotName, other.snapshotName) && Objects.equals(snapshotName, other.snapshotName)
&& Objects.equals(shrinkIndexName, other.shrinkIndexName) && Objects.equals(shrinkIndexName, other.shrinkIndexName)
&& Objects.equals(stepInfo, other.stepInfo) && Objects.equals(stepInfo, other.stepInfo)
&& Objects.equals(previousStepInfo, other.previousStepInfo)
&& Objects.equals(phaseExecutionInfo, other.phaseExecutionInfo); && Objects.equals(phaseExecutionInfo, other.phaseExecutionInfo);
} }

View file

@ -73,6 +73,7 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
stepNull ? null : randomAlphaOfLength(10), stepNull ? null : randomAlphaOfLength(10),
stepNull ? null : randomAlphaOfLength(10), stepNull ? null : randomAlphaOfLength(10),
randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()), randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()),
randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()),
randomBoolean() ? null : PhaseExecutionInfoTests.randomPhaseExecutionInfo("") randomBoolean() ? null : PhaseExecutionInfoTests.randomPhaseExecutionInfo("")
); );
} }
@ -99,6 +100,7 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
randomBoolean() ? null : randomAlphaOfLength(10), randomBoolean() ? null : randomAlphaOfLength(10),
randomBoolean() ? null : randomAlphaOfLength(10), randomBoolean() ? null : randomAlphaOfLength(10),
randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()), randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()),
randomBoolean() ? null : new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString()),
randomBoolean() ? null : PhaseExecutionInfoTests.randomPhaseExecutionInfo("") randomBoolean() ? null : PhaseExecutionInfoTests.randomPhaseExecutionInfo("")
) )
); );
@ -132,6 +134,7 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
null, null,
null, null,
null, null,
null,
null null
); );
assertThat(managedExplainResponse.getLifecycleDate(), is(notNullValue())); assertThat(managedExplainResponse.getLifecycleDate(), is(notNullValue()));
@ -191,42 +194,32 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
String shrinkIndexName = instance.getShrinkIndexName(); String shrinkIndexName = instance.getShrinkIndexName();
boolean managed = instance.managedByILM(); boolean managed = instance.managedByILM();
BytesReference stepInfo = instance.getStepInfo(); BytesReference stepInfo = instance.getStepInfo();
BytesReference previousStepInfo = instance.getPreviousStepInfo();
PhaseExecutionInfo phaseExecutionInfo = instance.getPhaseExecutionInfo(); PhaseExecutionInfo phaseExecutionInfo = instance.getPhaseExecutionInfo();
if (managed) { if (managed) {
switch (between(0, 14)) { switch (between(0, 15)) {
case 0: case 0 -> index += randomAlphaOfLengthBetween(1, 5);
index = index + randomAlphaOfLengthBetween(1, 5); case 1 -> policy += randomAlphaOfLengthBetween(1, 5);
break; case 2 -> {
case 1:
policy = policy + randomAlphaOfLengthBetween(1, 5);
break;
case 2:
phase = randomAlphaOfLengthBetween(1, 5); phase = randomAlphaOfLengthBetween(1, 5);
action = randomAlphaOfLengthBetween(1, 5); action = randomAlphaOfLengthBetween(1, 5);
step = randomAlphaOfLengthBetween(1, 5); step = randomAlphaOfLengthBetween(1, 5);
break; }
case 3: case 3 -> phaseTime = randomValueOtherThan(phaseTime, () -> randomLongBetween(0, 100000));
phaseTime = randomValueOtherThan(phaseTime, () -> randomLongBetween(0, 100000)); case 4 -> actionTime = randomValueOtherThan(actionTime, () -> randomLongBetween(0, 100000));
break; case 5 -> stepTime = randomValueOtherThan(stepTime, () -> randomLongBetween(0, 100000));
case 4: case 6 -> {
actionTime = randomValueOtherThan(actionTime, () -> randomLongBetween(0, 100000));
break;
case 5:
stepTime = randomValueOtherThan(stepTime, () -> randomLongBetween(0, 100000));
break;
case 6:
if (Strings.hasLength(failedStep) == false) { if (Strings.hasLength(failedStep) == false) {
failedStep = randomAlphaOfLength(10); failedStep = randomAlphaOfLength(10);
} else if (randomBoolean()) { } else if (randomBoolean()) {
failedStep = failedStep + randomAlphaOfLengthBetween(1, 5); failedStep += randomAlphaOfLengthBetween(1, 5);
} else { } else {
failedStep = null; failedStep = null;
} }
break; }
case 7: case 7 -> policyTime = randomValueOtherThan(policyTime, () -> randomLongBetween(0, 100000));
policyTime = randomValueOtherThan(policyTime, () -> randomLongBetween(0, 100000)); case 8 -> {
break;
case 8:
if (Strings.hasLength(stepInfo) == false) { if (Strings.hasLength(stepInfo) == false) {
stepInfo = new BytesArray(randomByteArrayOfLength(100)); stepInfo = new BytesArray(randomByteArrayOfLength(100));
} else if (randomBoolean()) { } else if (randomBoolean()) {
@ -237,31 +230,36 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
} else { } else {
stepInfo = null; stepInfo = null;
} }
break; }
case 9: case 9 -> {
phaseExecutionInfo = randomValueOtherThan( if (Strings.hasLength(previousStepInfo) == false) {
phaseExecutionInfo, previousStepInfo = new BytesArray(randomByteArrayOfLength(100));
() -> PhaseExecutionInfoTests.randomPhaseExecutionInfo("") } else if (randomBoolean()) {
); previousStepInfo = randomValueOtherThan(
break; previousStepInfo,
case 10: () -> new BytesArray(new RandomStepInfo(() -> randomAlphaOfLength(10)).toString())
);
} else {
previousStepInfo = null;
}
}
case 10 -> phaseExecutionInfo = randomValueOtherThan(
phaseExecutionInfo,
() -> PhaseExecutionInfoTests.randomPhaseExecutionInfo("")
);
case 11 -> {
return IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index); return IndexLifecycleExplainResponse.newUnmanagedIndexResponse(index);
case 11: }
case 12 -> {
isAutoRetryableError = true; isAutoRetryableError = true;
failedStepRetryCount = randomValueOtherThan(failedStepRetryCount, () -> randomInt(10)); failedStepRetryCount = randomValueOtherThan(failedStepRetryCount, () -> randomInt(10));
break; }
case 12: case 13 -> repositoryName = randomValueOtherThan(repositoryName, () -> randomAlphaOfLengthBetween(5, 10));
repositoryName = randomValueOtherThan(repositoryName, () -> randomAlphaOfLengthBetween(5, 10)); case 14 -> snapshotName = randomValueOtherThan(snapshotName, () -> randomAlphaOfLengthBetween(5, 10));
break; case 15 -> shrinkIndexName = randomValueOtherThan(shrinkIndexName, () -> randomAlphaOfLengthBetween(5, 10));
case 13: default -> throw new AssertionError("Illegal randomisation branch");
snapshotName = randomValueOtherThan(snapshotName, () -> randomAlphaOfLengthBetween(5, 10));
break;
case 14:
shrinkIndexName = randomValueOtherThan(shrinkIndexName, () -> randomAlphaOfLengthBetween(5, 10));
break;
default:
throw new AssertionError("Illegal randomisation branch");
} }
return IndexLifecycleExplainResponse.newManagedIndexResponse( return IndexLifecycleExplainResponse.newManagedIndexResponse(
index, index,
indexCreationDate, indexCreationDate,
@ -280,6 +278,7 @@ public class IndexLifecycleExplainResponseTests extends AbstractXContentSerializ
snapshotName, snapshotName,
shrinkIndexName, shrinkIndexName,
stepInfo, stepInfo,
previousStepInfo,
phaseExecutionInfo phaseExecutionInfo
); );
} else { } else {

View file

@ -67,11 +67,7 @@ public class LifecycleExecutionStateTests extends ESTestCase {
public void testEqualsAndHashcode() { public void testEqualsAndHashcode() {
LifecycleExecutionState original = LifecycleExecutionState.fromCustomMetadata(createCustomMetadata()); LifecycleExecutionState original = LifecycleExecutionState.fromCustomMetadata(createCustomMetadata());
EqualsHashCodeTestUtils.checkEqualsAndHashCode( EqualsHashCodeTestUtils.checkEqualsAndHashCode(original, toCopy -> LifecycleExecutionState.builder(toCopy).build(), this::mutate);
original,
toCopy -> LifecycleExecutionState.builder(toCopy).build(),
LifecycleExecutionStateTests::mutate
);
} }
public void testGetCurrentStepKey() { public void testGetCurrentStepKey() {
@ -133,78 +129,46 @@ public class LifecycleExecutionStateTests extends ESTestCase {
assertNull(error6.getMessage()); assertNull(error6.getMessage());
} }
private static LifecycleExecutionState mutate(LifecycleExecutionState toMutate) { private LifecycleExecutionState mutate(LifecycleExecutionState toMutate) {
LifecycleExecutionState.Builder newState = LifecycleExecutionState.builder(toMutate); LifecycleExecutionState.Builder newState = LifecycleExecutionState.builder(toMutate);
switch (randomIntBetween(0, 17)) { switch (randomIntBetween(0, 18)) {
case 0: case 0 -> newState.setPhase(randomValueOtherThan(toMutate.phase(), this::randomString));
newState.setPhase(randomValueOtherThan(toMutate.phase(), () -> randomAlphaOfLengthBetween(5, 20))); case 1 -> newState.setAction(randomValueOtherThan(toMutate.action(), this::randomString));
break; case 2 -> newState.setStep(randomValueOtherThan(toMutate.step(), this::randomString));
case 1: case 3 -> newState.setPhaseDefinition(randomValueOtherThan(toMutate.phaseDefinition(), this::randomString));
newState.setAction(randomValueOtherThan(toMutate.action(), () -> randomAlphaOfLengthBetween(5, 20))); case 4 -> newState.setFailedStep(randomValueOtherThan(toMutate.failedStep(), this::randomString));
break; case 5 -> newState.setStepInfo(randomValueOtherThan(toMutate.stepInfo(), this::randomString));
case 2: case 6 -> newState.setPreviousStepInfo(randomValueOtherThan(toMutate.previousStepInfo(), this::randomString));
newState.setStep(randomValueOtherThan(toMutate.step(), () -> randomAlphaOfLengthBetween(5, 20))); case 7 -> newState.setPhaseTime(randomValueOtherThan(toMutate.phaseTime(), ESTestCase::randomLong));
break; case 8 -> newState.setActionTime(randomValueOtherThan(toMutate.actionTime(), ESTestCase::randomLong));
case 3: case 9 -> newState.setStepTime(randomValueOtherThan(toMutate.stepTime(), ESTestCase::randomLong));
newState.setPhaseDefinition(randomValueOtherThan(toMutate.phaseDefinition(), () -> randomAlphaOfLengthBetween(5, 20))); case 10 -> newState.setIndexCreationDate(randomValueOtherThan(toMutate.lifecycleDate(), ESTestCase::randomLong));
break; case 11 -> newState.setShrinkIndexName(randomValueOtherThan(toMutate.shrinkIndexName(), this::randomString));
case 4: case 12 -> newState.setSnapshotRepository(randomValueOtherThan(toMutate.snapshotRepository(), this::randomString));
newState.setFailedStep(randomValueOtherThan(toMutate.failedStep(), () -> randomAlphaOfLengthBetween(5, 20))); case 13 -> newState.setSnapshotIndexName(randomValueOtherThan(toMutate.snapshotIndexName(), this::randomString));
break; case 14 -> newState.setSnapshotName(randomValueOtherThan(toMutate.snapshotName(), this::randomString));
case 5: case 15 -> newState.setDownsampleIndexName(randomValueOtherThan(toMutate.downsampleIndexName(), this::randomString));
newState.setStepInfo(randomValueOtherThan(toMutate.stepInfo(), () -> randomAlphaOfLengthBetween(5, 20))); case 16 -> newState.setIsAutoRetryableError(randomValueOtherThan(toMutate.isAutoRetryableError(), ESTestCase::randomBoolean));
break; case 17 -> newState.setFailedStepRetryCount(randomValueOtherThan(toMutate.failedStepRetryCount(), ESTestCase::randomInt));
case 6: case 18 -> {
newState.setPhaseTime(randomValueOtherThan(toMutate.phaseTime(), ESTestCase::randomLong)); return LifecycleExecutionState.EMPTY_STATE;
break; }
case 7: default -> throw new IllegalStateException("unknown randomization branch");
newState.setActionTime(randomValueOtherThan(toMutate.actionTime(), ESTestCase::randomLong));
break;
case 8:
newState.setStepTime(randomValueOtherThan(toMutate.stepTime(), ESTestCase::randomLong));
break;
case 9:
newState.setIndexCreationDate(randomValueOtherThan(toMutate.lifecycleDate(), ESTestCase::randomLong));
break;
case 10:
newState.setShrinkIndexName(randomValueOtherThan(toMutate.shrinkIndexName(), () -> randomAlphaOfLengthBetween(5, 20)));
break;
case 11:
newState.setSnapshotRepository(
randomValueOtherThan(toMutate.snapshotRepository(), () -> randomAlphaOfLengthBetween(5, 20))
);
break;
case 12:
newState.setSnapshotIndexName(randomValueOtherThan(toMutate.snapshotIndexName(), () -> randomAlphaOfLengthBetween(5, 20)));
break;
case 13:
newState.setSnapshotName(randomValueOtherThan(toMutate.snapshotName(), () -> randomAlphaOfLengthBetween(5, 20)));
break;
case 14:
newState.setDownsampleIndexName(
randomValueOtherThan(toMutate.downsampleIndexName(), () -> randomAlphaOfLengthBetween(5, 20))
);
break;
case 15:
newState.setIsAutoRetryableError(randomValueOtherThan(toMutate.isAutoRetryableError(), ESTestCase::randomBoolean));
break;
case 16:
newState.setFailedStepRetryCount(randomValueOtherThan(toMutate.failedStepRetryCount(), ESTestCase::randomInt));
break;
case 17:
return LifecycleExecutionState.builder().build();
default:
throw new IllegalStateException("unknown randomization branch");
} }
return newState.build(); return newState.build();
} }
private String randomString() {
return randomAlphaOfLengthBetween(5, 20);
}
static Map<String, String> createCustomMetadata() { static Map<String, String> createCustomMetadata() {
String phase = randomAlphaOfLengthBetween(5, 20); String phase = randomAlphaOfLengthBetween(5, 20);
String action = randomAlphaOfLengthBetween(5, 20); String action = randomAlphaOfLengthBetween(5, 20);
String step = randomAlphaOfLengthBetween(5, 20); String step = randomAlphaOfLengthBetween(5, 20);
String failedStep = randomAlphaOfLengthBetween(5, 20); String failedStep = randomAlphaOfLengthBetween(5, 20);
String stepInfo = randomAlphaOfLengthBetween(15, 50); String stepInfo = randomAlphaOfLengthBetween(15, 50);
String previousStepInfo = randomAlphaOfLengthBetween(15, 50);
String phaseDefinition = randomAlphaOfLengthBetween(15, 50); String phaseDefinition = randomAlphaOfLengthBetween(15, 50);
String repositoryName = randomAlphaOfLengthBetween(10, 20); String repositoryName = randomAlphaOfLengthBetween(10, 20);
String snapshotName = randomAlphaOfLengthBetween(10, 20); String snapshotName = randomAlphaOfLengthBetween(10, 20);
@ -220,6 +184,7 @@ public class LifecycleExecutionStateTests extends ESTestCase {
customMetadata.put("step", step); customMetadata.put("step", step);
customMetadata.put("failed_step", failedStep); customMetadata.put("failed_step", failedStep);
customMetadata.put("step_info", stepInfo); customMetadata.put("step_info", stepInfo);
customMetadata.put("previous_step_info", previousStepInfo);
customMetadata.put("phase_definition", phaseDefinition); customMetadata.put("phase_definition", phaseDefinition);
customMetadata.put("creation_date", String.valueOf(indexCreationDate)); customMetadata.put("creation_date", String.valueOf(indexCreationDate));
customMetadata.put("phase_time", String.valueOf(phaseTime)); customMetadata.put("phase_time", String.valueOf(phaseTime));

View file

@ -30,6 +30,7 @@ import org.elasticsearch.xpack.core.ilm.RolloverAction;
import org.elasticsearch.xpack.core.ilm.ShrinkAction; import org.elasticsearch.xpack.core.ilm.ShrinkAction;
import org.junit.Before; import org.junit.Before;
import java.util.Formatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -42,6 +43,7 @@ import static org.elasticsearch.xpack.TimeSeriesRestDriver.explain;
import static org.elasticsearch.xpack.TimeSeriesRestDriver.explainIndex; import static org.elasticsearch.xpack.TimeSeriesRestDriver.explainIndex;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -257,6 +259,64 @@ public class ExplainLifecycleIT extends ESRestTestCase {
); );
} }
public void testStepInfoPreservedOnAutoRetry() throws Exception {
String policyName = "policy-" + randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
Request createPolice = new Request("PUT", "_ilm/policy/" + policyName);
createPolice.setJsonEntity("""
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_docs": 1
}
}
}
}
}
}
""");
assertOK(client().performRequest(createPolice));
String aliasName = "step-info-test";
String indexName = aliasName + "-" + randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
Request templateRequest = new Request("PUT", "_index_template/template_" + policyName);
String templateBodyTemplate = """
{
"index_patterns": ["%s-*"],
"template": {
"settings": {
"index.lifecycle.name": "%s",
"index.lifecycle.rollover_alias": "%s"
}
}
}
""";
Formatter formatter = new Formatter(Locale.ROOT);
templateRequest.setJsonEntity(formatter.format(templateBodyTemplate, aliasName, policyName, aliasName).toString());
assertOK(client().performRequest(templateRequest));
Request indexRequest = new Request("POST", "/" + indexName + "/_doc/1");
indexRequest.setJsonEntity("{\"test\":\"value\"}");
assertOK(client().performRequest(indexRequest));
assertBusy(() -> {
Map<String, Object> explainIndex = explainIndex(client(), indexName);
assertThat(explainIndex.get("failed_step_retry_count"), notNullValue());
assertThat(explainIndex.get("previous_step_info"), notNullValue());
assertThat((int) explainIndex.get("failed_step_retry_count"), greaterThan(0));
assertThat(
explainIndex.get("previous_step_info").toString(),
containsString("rollover_alias [" + aliasName + "] does not point to index [" + indexName + "]")
);
});
}
private void assertUnmanagedIndex(Map<String, Object> explainIndexMap) { private void assertUnmanagedIndex(Map<String, Object> explainIndexMap) {
assertThat(explainIndexMap.get("managed"), is(false)); assertThat(explainIndexMap.get("managed"), is(false));
assertThat(explainIndexMap.get("time_since_index_creation"), is(nullValue())); assertThat(explainIndexMap.get("time_since_index_creation"), is(nullValue()));

View file

@ -289,6 +289,7 @@ public final class IndexLifecycleTransition {
// clear any step info or error-related settings from the current step // clear any step info or error-related settings from the current step
updatedState.setFailedStep(null); updatedState.setFailedStep(null);
updatedState.setPreviousStepInfo(existingState.stepInfo());
updatedState.setStepInfo(null); updatedState.setStepInfo(null);
updatedState.setIsAutoRetryableError(null); updatedState.setIsAutoRetryableError(null);
updatedState.setFailedStepRetryCount(null); updatedState.setFailedStepRetryCount(null);
@ -389,6 +390,7 @@ public final class IndexLifecycleTransition {
updatedState.setStep(nextStep.name()); updatedState.setStep(nextStep.name());
updatedState.setStepTime(nowAsMillis); updatedState.setStepTime(nowAsMillis);
updatedState.setFailedStep(null); updatedState.setFailedStep(null);
updatedState.setPreviousStepInfo(existingState.stepInfo());
updatedState.setStepInfo(null); updatedState.setStepInfo(null);
updatedState.setIsAutoRetryableError(null); updatedState.setIsAutoRetryableError(null);
updatedState.setFailedStepRetryCount(null); updatedState.setFailedStepRetryCount(null);

View file

@ -127,10 +127,15 @@ public class TransportExplainLifecycleAction extends TransportClusterInfoAction<
String policyName = indexMetadata.getLifecyclePolicyName(); String policyName = indexMetadata.getLifecyclePolicyName();
String currentPhase = lifecycleState.phase(); String currentPhase = lifecycleState.phase();
String stepInfo = lifecycleState.stepInfo(); String stepInfo = lifecycleState.stepInfo();
String previousStepInfo = lifecycleState.previousStepInfo();
BytesArray stepInfoBytes = null; BytesArray stepInfoBytes = null;
if (stepInfo != null) { if (stepInfo != null) {
stepInfoBytes = new BytesArray(stepInfo); stepInfoBytes = new BytesArray(stepInfo);
} }
BytesArray previousStepInfoBytes = null;
if (previousStepInfo != null) {
previousStepInfoBytes = new BytesArray(previousStepInfo);
}
Long indexCreationDate = indexMetadata.getCreationDate(); Long indexCreationDate = indexMetadata.getCreationDate();
// parse existing phase steps from the phase definition in the index settings // parse existing phase steps from the phase definition in the index settings
@ -182,6 +187,7 @@ public class TransportExplainLifecycleAction extends TransportClusterInfoAction<
lifecycleState.snapshotName(), lifecycleState.snapshotName(),
lifecycleState.shrinkIndexName(), lifecycleState.shrinkIndexName(),
stepInfoBytes, stepInfoBytes,
previousStepInfoBytes,
phaseExecutionInfo phaseExecutionInfo
); );
} else { } else {

View file

@ -896,7 +896,7 @@ public class IndexLifecycleTransitionTests extends ESTestCase {
); );
} }
public void testMoveClusterStateToPreviouslyFailedStepAsAutomaticRetry() { public void testMoveClusterStateToPreviouslyFailedStepAsAutomaticRetryAndSetsPreviousStepInfo() {
String indexName = "my_index"; String indexName = "my_index";
String policyName = "my_policy"; String policyName = "my_policy";
long now = randomNonNegativeLong(); long now = randomNonNegativeLong();
@ -921,6 +921,8 @@ public class IndexLifecycleTransitionTests extends ESTestCase {
lifecycleState.setStep(errorStepKey.name()); lifecycleState.setStep(errorStepKey.name());
lifecycleState.setStepTime(now); lifecycleState.setStepTime(now);
lifecycleState.setFailedStep(failedStepKey.name()); lifecycleState.setFailedStep(failedStepKey.name());
String initialStepInfo = randomAlphaOfLengthBetween(10, 50);
lifecycleState.setStepInfo(initialStepInfo);
ClusterState clusterState = buildClusterState( ClusterState clusterState = buildClusterState(
indexName, indexName,
indexSettingsBuilder, indexSettingsBuilder,
@ -938,6 +940,7 @@ public class IndexLifecycleTransitionTests extends ESTestCase {
IndexLifecycleRunnerTests.assertClusterStateOnNextStep(clusterState, index, errorStepKey, failedStepKey, nextClusterState, now); IndexLifecycleRunnerTests.assertClusterStateOnNextStep(clusterState, index, errorStepKey, failedStepKey, nextClusterState, now);
LifecycleExecutionState executionState = nextClusterState.metadata().index(indexName).getLifecycleExecutionState(); LifecycleExecutionState executionState = nextClusterState.metadata().index(indexName).getLifecycleExecutionState();
assertThat(executionState.failedStepRetryCount(), is(1)); assertThat(executionState.failedStepRetryCount(), is(1));
assertThat(executionState.previousStepInfo(), is(initialStepInfo));
} }
public void testMoveToFailedStepDoesntRefreshCachedPhaseWhenUnsafe() { public void testMoveToFailedStepDoesntRefreshCachedPhaseWhenUnsafe() {