Ignore unknown fields if overriding node metadata (#44689)
The `elasticsearch-node override-version` command fails if it cannot read the existing node metadata file. However, it reads this file strictly and fails if there are any unknown fields, which means it will not be useful if we add another field in future. This commit adds leniency to this command, allowing it to ignore any unknown fields and proceed with the downgrade. A downgrade is already unsafe, and the user is already copiously warned about this, so being lenient in this case does not make things much worse.
This commit is contained in:
parent
6928a315c4
commit
ee23968f05
|
@ -37,8 +37,8 @@ import java.util.Objects;
|
|||
*/
|
||||
public final class NodeMetaData {
|
||||
|
||||
private static final String NODE_ID_KEY = "node_id";
|
||||
private static final String NODE_VERSION_KEY = "node_version";
|
||||
static final String NODE_ID_KEY = "node_id";
|
||||
static final String NODE_VERSION_KEY = "node_version";
|
||||
|
||||
private final String nodeId;
|
||||
|
||||
|
@ -71,13 +71,6 @@ public final class NodeMetaData {
|
|||
'}';
|
||||
}
|
||||
|
||||
private static ObjectParser<Builder, Void> PARSER = new ObjectParser<>("node_meta_data", Builder::new);
|
||||
|
||||
static {
|
||||
PARSER.declareString(Builder::setNodeId, new ParseField(NODE_ID_KEY));
|
||||
PARSER.declareInt(Builder::setNodeVersionId, new ParseField(NODE_VERSION_KEY));
|
||||
}
|
||||
|
||||
public String nodeId() {
|
||||
return nodeId;
|
||||
}
|
||||
|
@ -130,7 +123,20 @@ public final class NodeMetaData {
|
|||
}
|
||||
}
|
||||
|
||||
public static final MetaDataStateFormat<NodeMetaData> FORMAT = new MetaDataStateFormat<NodeMetaData>("node-") {
|
||||
static class NodeMetaDataStateFormat extends MetaDataStateFormat<NodeMetaData> {
|
||||
|
||||
private ObjectParser<Builder, Void> objectParser;
|
||||
|
||||
/**
|
||||
* @param ignoreUnknownFields whether to ignore unknown fields or not. Normally we are strict about this, but
|
||||
* {@link OverrideNodeVersionCommand} is lenient.
|
||||
*/
|
||||
NodeMetaDataStateFormat(boolean ignoreUnknownFields) {
|
||||
super("node-");
|
||||
objectParser = new ObjectParser<>("node_meta_data", ignoreUnknownFields, Builder::new);
|
||||
objectParser.declareString(Builder::setNodeId, new ParseField(NODE_ID_KEY));
|
||||
objectParser.declareInt(Builder::setNodeVersionId, new ParseField(NODE_VERSION_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XContentBuilder newXContentBuilder(XContentType type, OutputStream stream) throws IOException {
|
||||
|
@ -146,8 +152,10 @@ public final class NodeMetaData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public NodeMetaData fromXContent(XContentParser parser) {
|
||||
return PARSER.apply(parser, null).build();
|
||||
public NodeMetaData fromXContent(XContentParser parser) throws IOException {
|
||||
return objectParser.apply(parser, null).build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final MetaDataStateFormat<NodeMetaData> FORMAT = new NodeMetaDataStateFormat(false);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,8 @@ public class OverrideNodeVersionCommand extends ElasticsearchNodeCommand {
|
|||
@Override
|
||||
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException {
|
||||
final Path[] nodePaths = Arrays.stream(toNodePaths(dataPaths)).map(p -> p.path).toArray(Path[]::new);
|
||||
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, namedXContentRegistry, nodePaths);
|
||||
final NodeMetaData nodeMetaData
|
||||
= new NodeMetaData.NodeMetaDataStateFormat(true).loadLatestState(logger, namedXContentRegistry, nodePaths);
|
||||
if (nodeMetaData == null) {
|
||||
throw new ElasticsearchException(NO_METADATA_MESSAGE);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import org.elasticsearch.ElasticsearchException;
|
|||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cli.MockTerminal;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.gateway.MetaDataStateFormat;
|
||||
import org.elasticsearch.gateway.WriteStateException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
@ -29,9 +32,12 @@ import org.junit.Before;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.elasticsearch.env.NodeMetaData.NODE_ID_KEY;
|
||||
import static org.elasticsearch.env.NodeMetaData.NODE_VERSION_KEY;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
|
||||
public class OverrideNodeVersionCommandTests extends ESTestCase {
|
||||
|
||||
|
@ -152,4 +158,55 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
|
|||
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
|
||||
assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT));
|
||||
}
|
||||
|
||||
public void testLenientlyIgnoresExtraFields() throws Exception {
|
||||
final String nodeId = randomAlphaOfLength(10);
|
||||
final Version nodeVersion = NodeMetaDataTests.tooNewVersion();
|
||||
FutureNodeMetaData.FORMAT.writeAndCleanup(new FutureNodeMetaData(nodeId, nodeVersion, randomLong()), nodePaths);
|
||||
assertThat(expectThrows(ElasticsearchException.class,
|
||||
() -> NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths)),
|
||||
hasToString(containsString("unknown field [future_field]")));
|
||||
|
||||
final MockTerminal mockTerminal = new MockTerminal();
|
||||
mockTerminal.addTextInput(randomFrom("y", "Y"));
|
||||
new OverrideNodeVersionCommand().processNodePaths(mockTerminal, nodePaths, environment);
|
||||
assertThat(mockTerminal.getOutput(), allOf(
|
||||
containsString("data loss"),
|
||||
containsString("You should not use this tool"),
|
||||
containsString(Version.CURRENT.toString()),
|
||||
containsString(nodeVersion.toString()),
|
||||
containsString(OverrideNodeVersionCommand.SUCCESS_MESSAGE)));
|
||||
expectThrows(IllegalStateException.class, () -> mockTerminal.readText(""));
|
||||
|
||||
final NodeMetaData nodeMetaData = NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths);
|
||||
assertThat(nodeMetaData.nodeId(), equalTo(nodeId));
|
||||
assertThat(nodeMetaData.nodeVersion(), equalTo(Version.CURRENT));
|
||||
}
|
||||
|
||||
private static class FutureNodeMetaData {
|
||||
private final String nodeId;
|
||||
private final Version nodeVersion;
|
||||
private final long futureValue;
|
||||
|
||||
FutureNodeMetaData(String nodeId, Version nodeVersion, long futureValue) {
|
||||
this.nodeId = nodeId;
|
||||
this.nodeVersion = nodeVersion;
|
||||
this.futureValue = futureValue;
|
||||
}
|
||||
|
||||
static final MetaDataStateFormat<FutureNodeMetaData> FORMAT
|
||||
= new MetaDataStateFormat<FutureNodeMetaData>(NodeMetaData.FORMAT.getPrefix()) {
|
||||
@Override
|
||||
public void toXContent(XContentBuilder builder, FutureNodeMetaData state) throws IOException {
|
||||
builder.field(NODE_ID_KEY, state.nodeId);
|
||||
builder.field(NODE_VERSION_KEY, state.nodeVersion.id);
|
||||
builder.field("future_field", state.futureValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FutureNodeMetaData fromXContent(XContentParser parser) {
|
||||
throw new AssertionError("shouldn't be loading a FutureNodeMetaData");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue