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.
This commit is contained in:
parent
ffaac5a08a
commit
cce321a560
|
@ -183,7 +183,7 @@ public final class TaskInfo implements Writeable, ToXContent {
|
|||
}
|
||||
|
||||
public static final ConstructingObjectParser<TaskInfo, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"task_info", a -> {
|
||||
"task_info", true, a -> {
|
||||
int i = 0;
|
||||
TaskId id = new TaskId((String) a[i++], (Long) a[i++]);
|
||||
String type = (String) a[i++];
|
||||
|
@ -196,11 +196,11 @@ public final class TaskInfo implements Writeable, ToXContent {
|
|||
String parentTaskIdString = (String) a[i++];
|
||||
|
||||
RawTaskStatus status = statusBytes == null ? null : new RawTaskStatus(statusBytes);
|
||||
TaskId parentTaskId = parentTaskIdString == null ? TaskId.EMPTY_TASK_ID : new TaskId((String) parentTaskIdString);
|
||||
TaskId parentTaskId = parentTaskIdString == null ? TaskId.EMPTY_TASK_ID : new TaskId(parentTaskIdString);
|
||||
return new TaskInfo(id, type, action, description, status, startTime, runningTimeNanos, cancellable, parentTaskId);
|
||||
});
|
||||
static {
|
||||
// Note for the future: this has to be backwards compatible with all changes to the task storage format
|
||||
// Note for the future: this has to be backwards and forwards compatible with all changes to the task storage format
|
||||
PARSER.declareString(constructorArg(), new ParseField("node"));
|
||||
PARSER.declareLong(constructorArg(), new ParseField("id"));
|
||||
PARSER.declareString(constructorArg(), new ParseField("type"));
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
@ -65,7 +66,7 @@ public class TaskResultTests extends ESTestCase {
|
|||
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
|
||||
result.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
try (XContentBuilder shuffled = shuffleXContent(builder);
|
||||
XContentParser parser = createParser(shuffled)) {
|
||||
XContentParser parser = createParser(shuffled)) {
|
||||
read = TaskResult.PARSER.apply(parser, null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
@ -74,16 +75,52 @@ public class TaskResultTests extends ESTestCase {
|
|||
assertEquals(result, read);
|
||||
}
|
||||
|
||||
public void testTaskInfoIsForwardCompatible() throws IOException {
|
||||
TaskInfo taskInfo = randomTaskInfo();
|
||||
TaskInfo read;
|
||||
try (XContentBuilder builder = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())) {
|
||||
builder.startObject();
|
||||
taskInfo.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
try (XContentBuilder withExtraFields = addRandomUnknownFields(builder)) {
|
||||
try (XContentBuilder shuffled = shuffleXContent(withExtraFields)) {
|
||||
try (XContentParser parser = createParser(shuffled)) {
|
||||
read = TaskInfo.PARSER.apply(parser, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Error processing [" + taskInfo + "]", e);
|
||||
}
|
||||
assertEquals(taskInfo, read);
|
||||
}
|
||||
|
||||
private XContentBuilder addRandomUnknownFields(XContentBuilder builder) throws IOException {
|
||||
try (XContentParser parser = createParser(builder)) {
|
||||
Map<String, Object> map = parser.mapOrdered();
|
||||
int numberOfNewFields = randomIntBetween(2, 10);
|
||||
for (int i = 0; i < numberOfNewFields; i++) {
|
||||
if (randomBoolean()) {
|
||||
map.put("unknown_field" + i, randomAlphaOfLength(20));
|
||||
} else {
|
||||
map.put("unknown_field" + i, Collections.singletonMap("inner", randomAlphaOfLength(20)));
|
||||
}
|
||||
}
|
||||
XContentBuilder xContentBuilder = XContentFactory.contentBuilder(parser.contentType());
|
||||
return xContentBuilder.map(map);
|
||||
}
|
||||
}
|
||||
|
||||
private static TaskResult randomTaskResult() throws IOException {
|
||||
switch (between(0, 2)) {
|
||||
case 0:
|
||||
return new TaskResult(randomBoolean(), randomTaskInfo());
|
||||
case 1:
|
||||
return new TaskResult(randomTaskInfo(), new RuntimeException("error"));
|
||||
case 2:
|
||||
return new TaskResult(randomTaskInfo(), randomTaskResponse());
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported random TaskResult constructor");
|
||||
case 0:
|
||||
return new TaskResult(randomBoolean(), randomTaskInfo());
|
||||
case 1:
|
||||
return new TaskResult(randomTaskInfo(), new RuntimeException("error"));
|
||||
case 2:
|
||||
return new TaskResult(randomTaskInfo(), randomTaskResponse());
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported random TaskResult constructor");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue