Skip to content

Commit cce321a

Browse files
authored
Task Management: Make TaskInfo parsing forwards compatible (#24073)
TaskInfo is stored as a part of TaskResult and therefore can be read by nodes with an older version. If we add any additional information to TaskInfo (for #23250, for example), nodes with an older version should be able to ignore it, otherwise they will not be able to read TaskResults stored by newer nodes.
1 parent ffaac5a commit cce321a

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

core/src/main/java/org/elasticsearch/tasks/TaskInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
183183
}
184184

185185
public static final ConstructingObjectParser<TaskInfo, Void> PARSER = new ConstructingObjectParser<>(
186-
"task_info", a -> {
186+
"task_info", true, a -> {
187187
int i = 0;
188188
TaskId id = new TaskId((String) a[i++], (Long) a[i++]);
189189
String type = (String) a[i++];
@@ -196,11 +196,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
196196
String parentTaskIdString = (String) a[i++];
197197

198198
RawTaskStatus status = statusBytes == null ? null : new RawTaskStatus(statusBytes);
199-
TaskId parentTaskId = parentTaskIdString == null ? TaskId.EMPTY_TASK_ID : new TaskId((String) parentTaskIdString);
199+
TaskId parentTaskId = parentTaskIdString == null ? TaskId.EMPTY_TASK_ID : new TaskId(parentTaskIdString);
200200
return new TaskInfo(id, type, action, description, status, startTime, runningTimeNanos, cancellable, parentTaskId);
201201
});
202202
static {
203-
// Note for the future: this has to be backwards compatible with all changes to the task storage format
203+
// Note for the future: this has to be backwards and forwards compatible with all changes to the task storage format
204204
PARSER.declareString(constructorArg(), new ParseField("node"));
205205
PARSER.declareLong(constructorArg(), new ParseField("id"));
206206
PARSER.declareString(constructorArg(), new ParseField("type"));

core/src/test/java/org/elasticsearch/tasks/TaskResultTests.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.common.io.stream.StreamInput;
2727
import org.elasticsearch.common.xcontent.ToXContent;
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
29+
import org.elasticsearch.common.xcontent.XContentFactory;
2930
import org.elasticsearch.common.xcontent.XContentParser;
3031
import org.elasticsearch.common.xcontent.XContentType;
3132
import org.elasticsearch.test.ESTestCase;
@@ -65,7 +66,7 @@ public void testXContentRoundTrip() throws IOException {
6566
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
6667
result.toXContent(builder, ToXContent.EMPTY_PARAMS);
6768
try (XContentBuilder shuffled = shuffleXContent(builder);
68-
XContentParser parser = createParser(shuffled)) {
69+
XContentParser parser = createParser(shuffled)) {
6970
read = TaskResult.PARSER.apply(parser, null);
7071
}
7172
} catch (IOException e) {
@@ -74,16 +75,52 @@ public void testXContentRoundTrip() throws IOException {
7475
assertEquals(result, read);
7576
}
7677

78+
public void testTaskInfoIsForwardCompatible() throws IOException {
79+
TaskInfo taskInfo = randomTaskInfo();
80+
TaskInfo read;
81+
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
82+
builder.startObject();
83+
taskInfo.toXContent(builder, ToXContent.EMPTY_PARAMS);
84+
builder.endObject();
85+
try (XContentBuilder withExtraFields = addRandomUnknownFields(builder)) {
86+
try (XContentBuilder shuffled = shuffleXContent(withExtraFields)) {
87+
try (XContentParser parser = createParser(shuffled)) {
88+
read = TaskInfo.PARSER.apply(parser, null);
89+
}
90+
}
91+
}
92+
} catch (IOException e) {
93+
throw new IOException("Error processing [" + taskInfo + "]", e);
94+
}
95+
assertEquals(taskInfo, read);
96+
}
97+
98+
private XContentBuilder addRandomUnknownFields(XContentBuilder builder) throws IOException {
99+
try (XContentParser parser = createParser(builder)) {
100+
Map<String, Object> map = parser.mapOrdered();
101+
int numberOfNewFields = randomIntBetween(2, 10);
102+
for (int i = 0; i < numberOfNewFields; i++) {
103+
if (randomBoolean()) {
104+
map.put("unknown_field" + i, randomAlphaOfLength(20));
105+
} else {
106+
map.put("unknown_field" + i, Collections.singletonMap("inner", randomAlphaOfLength(20)));
107+
}
108+
}
109+
XContentBuilder xContentBuilder = XContentFactory.contentBuilder(parser.contentType());
110+
return xContentBuilder.map(map);
111+
}
112+
}
113+
77114
private static TaskResult randomTaskResult() throws IOException {
78115
switch (between(0, 2)) {
79-
case 0:
80-
return new TaskResult(randomBoolean(), randomTaskInfo());
81-
case 1:
82-
return new TaskResult(randomTaskInfo(), new RuntimeException("error"));
83-
case 2:
84-
return new TaskResult(randomTaskInfo(), randomTaskResponse());
85-
default:
86-
throw new UnsupportedOperationException("Unsupported random TaskResult constructor");
116+
case 0:
117+
return new TaskResult(randomBoolean(), randomTaskInfo());
118+
case 1:
119+
return new TaskResult(randomTaskInfo(), new RuntimeException("error"));
120+
case 2:
121+
return new TaskResult(randomTaskInfo(), randomTaskResponse());
122+
default:
123+
throw new UnsupportedOperationException("Unsupported random TaskResult constructor");
87124
}
88125
}
89126

0 commit comments

Comments
 (0)