Skip to content

Commit 3d5843a

Browse files
dakroneandreidan
andauthored
Allow ILM move-to-step without action or name (#75435)
* Allow ILM move-to-step without `action` or `name` This commit enhances ILM's move-to-step API to allow dropping the `name`, or dropping both the `action` and `name`. For example: ```json POST /_ilm/move/foo-1 { "current_step": { "phase": "hot", "action": "rollover", "name": "check-rollover-ready" }, "next_step": { "phase": "warm", "action": "forcemerge" } } ``` Will move to the first step in the `forcemerge` action in the `warm` phase (without having to know the specific step name). Another example: ```json POST /_ilm/move/foo-1 { "current_step": { "phase": "hot", "action": "rollover", "name": "check-rollover-ready" }, "next_step": { "phase": "warm" } } ``` Will move to the first step in the `warm` phase (without having to know the specific action name). Bear in mind that the execution order is still entirely an implementation detail, so "first" in the above sentences means the first step that ILM would execute. Resolves #58128 * Apply Andrei's wording change (thanks!) Co-authored-by: Andrei Dan <[email protected]> * Log index and policy name when the concrete step key can't be resolved Co-authored-by: Andrei Dan <[email protected]>
1 parent 1f04319 commit 3d5843a

File tree

7 files changed

+393
-19
lines changed

7 files changed

+393
-19
lines changed

docs/reference/ilm/apis/move-to-step.asciidoc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ The request will fail if the current step does not match the step currently
3636
being executed for the index. This is to prevent the index from being moved from
3737
an unexpected step into the next step.
3838

39+
When specifying the target (`next_step`) to which the index will be moved, either the `name` or both
40+
the `action` and `name` fields are optional. If only the phase is specified, the index will move to
41+
the first step of the first action in the target phase. If the phase and action are specified, the index will move to
42+
the first step of the specified action in the specified phase. Only actions specified in the ILM
43+
policy are considered valid, an index cannot move to a step that is not part of its policy.
44+
3945
[[ilm-move-to-step-path-params]]
4046
==== {api-path-parms-title}
4147

@@ -152,14 +158,16 @@ POST _ilm/move/my-index-000001
152158
},
153159
"next_step": { <2>
154160
"phase": "warm",
155-
"action": "forcemerge",
156-
"name": "forcemerge"
161+
"action": "forcemerge", <3>
162+
"name": "forcemerge" <4>
157163
}
158164
}
159165
--------------------------------------------------
160166
// TEST[continued]
161167
<1> The step that the index is expected to be in
162168
<2> The step that you want to execute
169+
<3> The optional action to which the index will be moved
170+
<4> The optional step name to which the index will be moved
163171

164172
If the request succeeds, you receive the following result:
165173

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/action/MoveToStepAction.java

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77
*/
88
package org.elasticsearch.xpack.core.ilm.action;
99

10+
import org.elasticsearch.Version;
1011
import org.elasticsearch.action.ActionRequestValidationException;
1112
import org.elasticsearch.action.ActionType;
1213
import org.elasticsearch.action.support.master.AcknowledgedRequest;
1314
import org.elasticsearch.action.support.master.AcknowledgedResponse;
14-
import org.elasticsearch.common.xcontent.ParseField;
1515
import org.elasticsearch.common.Strings;
1616
import org.elasticsearch.common.io.stream.StreamInput;
1717
import org.elasticsearch.common.io.stream.StreamOutput;
18+
import org.elasticsearch.common.io.stream.Writeable;
1819
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
20+
import org.elasticsearch.common.xcontent.ParseField;
1921
import org.elasticsearch.common.xcontent.ToXContentObject;
2022
import org.elasticsearch.common.xcontent.XContentBuilder;
2123
import org.elasticsearch.common.xcontent.XContentParser;
24+
import org.elasticsearch.core.Nullable;
2225
import org.elasticsearch.xpack.core.ilm.Step.StepKey;
2326

2427
import java.io.IOException;
@@ -39,19 +42,22 @@ public static class Request extends AcknowledgedRequest<Request> implements ToXC
3942
new ConstructingObjectParser<>("move_to_step_request", false,
4043
(a, index) -> {
4144
StepKey currentStepKey = (StepKey) a[0];
42-
StepKey nextStepKey = (StepKey) a[1];
45+
PartialStepKey nextStepKey = (PartialStepKey) a[1];
4346
return new Request(index, currentStepKey, nextStepKey);
4447
});
48+
4549
static {
50+
// The current step uses the strict parser (meaning it requires all three parts of a stepkey)
4651
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, name) -> StepKey.parse(p), CURRENT_KEY_FIELD);
47-
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, name) -> StepKey.parse(p), NEXT_KEY_FIELD);
52+
// The target step uses the parser that allows specifying only the phase, or the phase and action
53+
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, name) -> PartialStepKey.parse(p), NEXT_KEY_FIELD);
4854
}
4955

5056
private String index;
5157
private StepKey currentStepKey;
52-
private StepKey nextStepKey;
58+
private PartialStepKey nextStepKey;
5359

54-
public Request(String index, StepKey currentStepKey, StepKey nextStepKey) {
60+
public Request(String index, StepKey currentStepKey, PartialStepKey nextStepKey) {
5561
this.index = index;
5662
this.currentStepKey = currentStepKey;
5763
this.nextStepKey = nextStepKey;
@@ -61,7 +67,12 @@ public Request(StreamInput in) throws IOException {
6167
super(in);
6268
this.index = in.readString();
6369
this.currentStepKey = new StepKey(in);
64-
this.nextStepKey = new StepKey(in);
70+
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
71+
this.nextStepKey = new PartialStepKey(in);
72+
} else {
73+
StepKey spec = new StepKey(in);
74+
this.nextStepKey = new PartialStepKey(spec.getPhase(), spec.getAction(), spec.getName());
75+
}
6576
}
6677

6778
public Request() {
@@ -75,7 +86,7 @@ public StepKey getCurrentStepKey() {
7586
return currentStepKey;
7687
}
7788

78-
public StepKey getNextStepKey() {
89+
public PartialStepKey getNextStepKey() {
7990
return nextStepKey;
8091
}
8192

@@ -93,7 +104,14 @@ public void writeTo(StreamOutput out) throws IOException {
93104
super.writeTo(out);
94105
out.writeString(index);
95106
currentStepKey.writeTo(out);
96-
nextStepKey.writeTo(out);
107+
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
108+
nextStepKey.writeTo(out);
109+
} else {
110+
String action = nextStepKey.getAction();
111+
String name = nextStepKey.getName();
112+
StepKey key = new StepKey(nextStepKey.getPhase(), action == null ? "" : action, name == null ? "" : name);
113+
key.writeTo(out);
114+
}
97115
}
98116

99117
@Override
@@ -126,5 +144,110 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
126144
.field(NEXT_KEY_FIELD.getPreferredName(), nextStepKey)
127145
.endObject();
128146
}
147+
148+
/**
149+
* A PartialStepKey is like a {@link StepKey}, however, the action and step name are optional.
150+
*/
151+
public static class PartialStepKey implements Writeable, ToXContentObject {
152+
private final String phase;
153+
private final String action;
154+
private final String name;
155+
156+
public static final ParseField PHASE_FIELD = new ParseField("phase");
157+
public static final ParseField ACTION_FIELD = new ParseField("action");
158+
public static final ParseField NAME_FIELD = new ParseField("name");
159+
private static final ConstructingObjectParser<PartialStepKey, Void> PARSER =
160+
new ConstructingObjectParser<>("step_specification",
161+
a -> new PartialStepKey((String) a[0], (String) a[1], (String) a[2]));
162+
static {
163+
PARSER.declareString(ConstructingObjectParser.constructorArg(), PHASE_FIELD);
164+
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), ACTION_FIELD);
165+
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), NAME_FIELD);
166+
}
167+
168+
public PartialStepKey(String phase, @Nullable String action, @Nullable String name) {
169+
this.phase = phase;
170+
this.action = action;
171+
this.name = name;
172+
if (name != null && action == null) {
173+
throw new IllegalArgumentException("phase; phase and action; or phase, action, and step must be provided, " +
174+
"but a step name was specified without a corresponding action");
175+
}
176+
}
177+
178+
public PartialStepKey(StreamInput in) throws IOException {
179+
this.phase = in.readString();
180+
this.action = in.readOptionalString();
181+
this.name = in.readOptionalString();
182+
if (name != null && action == null) {
183+
throw new IllegalArgumentException("phase; phase and action; or phase, action, and step must be provided, " +
184+
"but a step name was specified without a corresponding action");
185+
}
186+
}
187+
188+
public static PartialStepKey parse(XContentParser parser) {
189+
return PARSER.apply(parser, null);
190+
}
191+
192+
@Override
193+
public void writeTo(StreamOutput out) throws IOException {
194+
out.writeString(phase);
195+
out.writeOptionalString(action);
196+
out.writeOptionalString(name);
197+
}
198+
199+
@Nullable
200+
public String getPhase() {
201+
return phase;
202+
}
203+
204+
@Nullable
205+
public String getAction() {
206+
return action;
207+
}
208+
209+
@Nullable
210+
public String getName() {
211+
return name;
212+
}
213+
214+
@Override
215+
public int hashCode() {
216+
return Objects.hash(phase, action, name);
217+
}
218+
219+
@Override
220+
public boolean equals(Object obj) {
221+
if (obj == null) {
222+
return false;
223+
}
224+
if (getClass() != obj.getClass()) {
225+
return false;
226+
}
227+
PartialStepKey other = (PartialStepKey) obj;
228+
return Objects.equals(phase, other.phase) &&
229+
Objects.equals(action, other.action) &&
230+
Objects.equals(name, other.name);
231+
}
232+
233+
@Override
234+
public String toString() {
235+
return Strings.toString(this);
236+
}
237+
238+
@Override
239+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
240+
builder.startObject();
241+
builder.field(PHASE_FIELD.getPreferredName(), phase);
242+
if (action != null) {
243+
builder.field(ACTION_FIELD.getPreferredName(), action);
244+
}
245+
if (name != null) {
246+
builder.field(NAME_FIELD.getPreferredName(), name);
247+
}
248+
builder.endObject();
249+
return builder;
250+
}
251+
}
129252
}
130253
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/action/MoveToStepRequestTests.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void setup() {
2727

2828
@Override
2929
protected Request createTestInstance() {
30-
return new Request(index, stepKeyTests.createTestInstance(), stepKeyTests.createTestInstance());
30+
return new Request(index, stepKeyTests.createTestInstance(), randomStepSpecification());
3131
}
3232

3333
@Override
@@ -49,7 +49,7 @@ protected boolean supportsUnknownFields() {
4949
protected Request mutateInstance(Request request) {
5050
String index = request.getIndex();
5151
StepKey currentStepKey = request.getCurrentStepKey();
52-
StepKey nextStepKey = request.getNextStepKey();
52+
Request.PartialStepKey nextStepKey = request.getNextStepKey();
5353

5454
switch (between(0, 2)) {
5555
case 0:
@@ -59,12 +59,24 @@ protected Request mutateInstance(Request request) {
5959
currentStepKey = stepKeyTests.mutateInstance(currentStepKey);
6060
break;
6161
case 2:
62-
nextStepKey = stepKeyTests.mutateInstance(nextStepKey);
62+
nextStepKey = randomValueOtherThan(nextStepKey, MoveToStepRequestTests::randomStepSpecification);
6363
break;
6464
default:
6565
throw new AssertionError("Illegal randomisation branch");
6666
}
6767

6868
return new Request(index, currentStepKey, nextStepKey);
6969
}
70+
71+
private static Request.PartialStepKey randomStepSpecification() {
72+
if (randomBoolean()) {
73+
StepKey key = stepKeyTests.createTestInstance();
74+
return new Request.PartialStepKey(key.getPhase(), key.getAction(), key.getName());
75+
} else {
76+
String phase = randomAlphaOfLength(10);
77+
String action = randomBoolean() ? null : randomAlphaOfLength(6);
78+
String name = action == null ? null : (randomBoolean() ? null : randomAlphaOfLength(6));
79+
return new Request.PartialStepKey(phase, action, name);
80+
}
81+
}
7082
}

0 commit comments

Comments
 (0)