Skip to content

Commit 53006b7

Browse files
committed
Use special XContent registry for node tool (#54050)
Fixes an issue where the elasticsearch-node command-line tools would not work correctly because PersistentTasksCustomMetaData contains named XContent from plugins. This PR makes it so that the parsing for all custom metadata is skipped, even if the core system would know how to handle it. Closes #53549
1 parent 0bd37a4 commit 53006b7

File tree

15 files changed

+327
-240
lines changed

15 files changed

+327
-240
lines changed

server/src/main/java/org/elasticsearch/cluster/coordination/ElasticsearchNodeCommand.java

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,36 @@
2525
import org.apache.logging.log4j.Logger;
2626
import org.apache.lucene.store.LockObtainFailedException;
2727
import org.elasticsearch.ElasticsearchException;
28+
import org.elasticsearch.Version;
29+
import org.elasticsearch.action.admin.indices.rollover.Condition;
2830
import org.elasticsearch.cli.EnvironmentAwareCommand;
2931
import org.elasticsearch.cli.Terminal;
3032
import org.elasticsearch.cli.UserException;
31-
import org.elasticsearch.cluster.ClusterModule;
3233
import org.elasticsearch.cluster.ClusterName;
3334
import org.elasticsearch.cluster.ClusterState;
35+
import org.elasticsearch.cluster.Diff;
36+
import org.elasticsearch.cluster.metadata.MetaData;
3437
import org.elasticsearch.common.collect.Tuple;
38+
import org.elasticsearch.common.io.stream.StreamOutput;
3539
import org.elasticsearch.common.settings.ClusterSettings;
3640
import org.elasticsearch.common.settings.Settings;
3741
import org.elasticsearch.common.util.BigArrays;
3842
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
43+
import org.elasticsearch.common.xcontent.XContentBuilder;
44+
import org.elasticsearch.common.xcontent.XContentParser;
3945
import org.elasticsearch.env.Environment;
4046
import org.elasticsearch.env.NodeEnvironment;
4147
import org.elasticsearch.env.NodeMetaData;
4248
import org.elasticsearch.gateway.PersistedClusterStateService;
43-
import org.elasticsearch.indices.IndicesModule;
4449

4550
import java.io.IOException;
4651
import java.nio.file.Files;
4752
import java.nio.file.Path;
4853
import java.util.Arrays;
54+
import java.util.Collections;
55+
import java.util.EnumSet;
56+
import java.util.Map;
4957
import java.util.Objects;
50-
import java.util.function.Function;
51-
import java.util.stream.Collectors;
52-
import java.util.stream.Stream;
5358

5459
public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
5560
private static final Logger logger = LogManager.getLogger(ElasticsearchNodeCommand.class);
@@ -67,10 +72,34 @@ public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
6772
protected static final String CS_MISSING_MSG =
6873
"cluster state is empty, cluster has never been bootstrapped?";
6974

70-
protected static final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(
71-
Stream.of(ClusterModule.getNamedXWriteables().stream(), IndicesModule.getNamedXContents().stream())
72-
.flatMap(Function.identity())
73-
.collect(Collectors.toList()));
75+
// fake the registry here, as command-line tools are not loading plugins, and ensure that it preserves the parsed XContent
76+
public static final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(Collections.emptyList()) {
77+
78+
@SuppressWarnings("unchecked")
79+
@Override
80+
public <T, C> T parseNamedObject(Class<T> categoryClass, String name, XContentParser parser, C context) throws IOException {
81+
// Currently, two unknown top-level objects are present
82+
if (MetaData.Custom.class.isAssignableFrom(categoryClass)) {
83+
return (T) new UnknownMetaDataCustom(name, parser.mapOrdered());
84+
}
85+
if (Condition.class.isAssignableFrom(categoryClass)) {
86+
// The parsing for conditions is a bit weird as these represent JSON primitives (strings or numbers)
87+
// TODO: Make Condition non-pluggable
88+
assert parser.currentToken() == XContentParser.Token.FIELD_NAME : parser.currentToken();
89+
if (parser.currentToken() != XContentParser.Token.FIELD_NAME) {
90+
throw new UnsupportedOperationException("Unexpected token for Condition: " + parser.currentToken());
91+
}
92+
parser.nextToken();
93+
assert parser.currentToken().isValue() : parser.currentToken();
94+
if (parser.currentToken().isValue() == false) {
95+
throw new UnsupportedOperationException("Unexpected token for Condition: " + parser.currentToken());
96+
}
97+
return (T) new UnknownCondition(name, parser.objectText());
98+
}
99+
assert false : "Unexpected category class " + categoryClass + " for name " + name;
100+
throw new UnsupportedOperationException("Unexpected category class " + categoryClass + " for name " + name);
101+
}
102+
};
74103

75104
public ElasticsearchNodeCommand(String description) {
76105
super(description);
@@ -86,7 +115,7 @@ public static PersistedClusterStateService createPersistedClusterStateService(Se
86115

87116
String nodeId = nodeMetaData.nodeId();
88117
return new PersistedClusterStateService(dataPaths, nodeId, namedXContentRegistry, BigArrays.NON_RECYCLING_INSTANCE,
89-
new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), () -> 0L, true);
118+
new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), () -> 0L);
90119
}
91120

92121
public static ClusterState clusterState(Environment environment, PersistedClusterStateService.OnDiskState onDiskState) {
@@ -176,4 +205,78 @@ private static NodeEnvironment.NodePath createNodePath(Path path) {
176205
OptionParser getParser() {
177206
return parser;
178207
}
208+
209+
public static class UnknownMetaDataCustom implements MetaData.Custom {
210+
211+
private final String name;
212+
private final Map<String, Object> contents;
213+
214+
public UnknownMetaDataCustom(String name, Map<String, Object> contents) {
215+
this.name = name;
216+
this.contents = contents;
217+
}
218+
219+
@Override
220+
public EnumSet<MetaData.XContentContext> context() {
221+
return EnumSet.of(MetaData.XContentContext.API, MetaData.XContentContext.GATEWAY);
222+
}
223+
224+
@Override
225+
public Diff<MetaData.Custom> diff(MetaData.Custom previousState) {
226+
assert false;
227+
throw new UnsupportedOperationException();
228+
}
229+
230+
@Override
231+
public String getWriteableName() {
232+
return name;
233+
}
234+
235+
@Override
236+
public Version getMinimalSupportedVersion() {
237+
assert false;
238+
throw new UnsupportedOperationException();
239+
}
240+
241+
@Override
242+
public void writeTo(StreamOutput out) throws IOException {
243+
assert false;
244+
throw new UnsupportedOperationException();
245+
}
246+
247+
@Override
248+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
249+
return builder.mapContents(contents);
250+
}
251+
}
252+
253+
public static class UnknownCondition extends Condition<Object> {
254+
255+
public UnknownCondition(String name, Object value) {
256+
super(name);
257+
this.value = value;
258+
}
259+
260+
@Override
261+
public String getWriteableName() {
262+
return name;
263+
}
264+
265+
@Override
266+
public void writeTo(StreamOutput out) throws IOException {
267+
assert false;
268+
throw new UnsupportedOperationException();
269+
}
270+
271+
@Override
272+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
273+
return builder.field(name, value);
274+
}
275+
276+
@Override
277+
public Result evaluate(Stats stats) {
278+
assert false;
279+
throw new UnsupportedOperationException();
280+
}
281+
}
179282
}

server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ public static Diff<MetaData> readDiffFrom(StreamInput in) throws IOException {
813813
}
814814

815815
public static MetaData fromXContent(XContentParser parser) throws IOException {
816-
return Builder.fromXContent(parser, false);
816+
return Builder.fromXContent(parser);
817817
}
818818

819819
@Override
@@ -1353,7 +1353,7 @@ public static void toXContent(MetaData metaData, XContentBuilder builder, ToXCon
13531353
builder.endObject();
13541354
}
13551355

1356-
public static MetaData fromXContent(XContentParser parser, boolean preserveUnknownCustoms) throws IOException {
1356+
public static MetaData fromXContent(XContentParser parser) throws IOException {
13571357
Builder builder = new Builder();
13581358

13591359
// we might get here after the meta-data element, or on a fresh parser
@@ -1403,13 +1403,8 @@ public static MetaData fromXContent(XContentParser parser, boolean preserveUnkno
14031403
Custom custom = parser.namedObject(Custom.class, currentFieldName, null);
14041404
builder.putCustom(custom.getWriteableName(), custom);
14051405
} catch (NamedObjectNotFoundException ex) {
1406-
if (preserveUnknownCustoms) {
1407-
logger.warn("Adding unknown custom object with type {}", currentFieldName);
1408-
builder.putCustom(currentFieldName, new UnknownGatewayOnlyCustom(parser.mapOrdered()));
1409-
} else {
1410-
logger.warn("Skipping unknown custom object with type {}", currentFieldName);
1411-
parser.skipChildren();
1412-
}
1406+
logger.warn("Skipping unknown custom object with type {}", currentFieldName);
1407+
parser.skipChildren();
14131408
}
14141409
}
14151410
} else if (token.isValue()) {
@@ -1430,45 +1425,6 @@ public static MetaData fromXContent(XContentParser parser, boolean preserveUnkno
14301425
}
14311426
}
14321427

1433-
public static class UnknownGatewayOnlyCustom implements Custom {
1434-
1435-
private final Map<String, Object> contents;
1436-
1437-
UnknownGatewayOnlyCustom(Map<String, Object> contents) {
1438-
this.contents = contents;
1439-
}
1440-
1441-
@Override
1442-
public EnumSet<XContentContext> context() {
1443-
return EnumSet.of(MetaData.XContentContext.API, MetaData.XContentContext.GATEWAY);
1444-
}
1445-
1446-
@Override
1447-
public Diff<Custom> diff(Custom previousState) {
1448-
throw new UnsupportedOperationException();
1449-
}
1450-
1451-
@Override
1452-
public String getWriteableName() {
1453-
throw new UnsupportedOperationException();
1454-
}
1455-
1456-
@Override
1457-
public Version getMinimalSupportedVersion() {
1458-
throw new UnsupportedOperationException();
1459-
}
1460-
1461-
@Override
1462-
public void writeTo(StreamOutput out) throws IOException {
1463-
throw new UnsupportedOperationException();
1464-
}
1465-
1466-
@Override
1467-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
1468-
return builder.mapContents(contents);
1469-
}
1470-
}
1471-
14721428
private static final ToXContent.Params FORMAT_PARAMS;
14731429
static {
14741430
Map<String, String> params = new HashMap<>(2);
@@ -1480,25 +1436,17 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
14801436
/**
14811437
* State format for {@link MetaData} to write to and load from disk
14821438
*/
1483-
public static final MetaDataStateFormat<MetaData> FORMAT = createMetaDataStateFormat(false);
1439+
public static final MetaDataStateFormat<MetaData> FORMAT = new MetaDataStateFormat<MetaData>(GLOBAL_STATE_FILE_PREFIX) {
14841440

1485-
/**
1486-
* Special state format for {@link MetaData} to write to and load from disk, preserving unknown customs
1487-
*/
1488-
public static final MetaDataStateFormat<MetaData> FORMAT_PRESERVE_CUSTOMS = createMetaDataStateFormat(true);
1489-
1490-
private static MetaDataStateFormat<MetaData> createMetaDataStateFormat(boolean preserveUnknownCustoms) {
1491-
return new MetaDataStateFormat<MetaData>(GLOBAL_STATE_FILE_PREFIX) {
1441+
@Override
1442+
public void toXContent(XContentBuilder builder, MetaData state) throws IOException {
1443+
Builder.toXContent(state, builder, FORMAT_PARAMS);
1444+
}
14921445

1493-
@Override
1494-
public void toXContent(XContentBuilder builder, MetaData state) throws IOException {
1495-
Builder.toXContent(state, builder, FORMAT_PARAMS);
1496-
}
1446+
@Override
1447+
public MetaData fromXContent(XContentParser parser) throws IOException {
1448+
return Builder.fromXContent(parser);
1449+
}
1450+
};
14971451

1498-
@Override
1499-
public MetaData fromXContent(XContentParser parser) throws IOException {
1500-
return Builder.fromXContent(parser, preserveUnknownCustoms);
1501-
}
1502-
};
1503-
}
15041452
}

server/src/main/java/org/elasticsearch/gateway/PersistedClusterStateService.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,26 +136,23 @@ public class PersistedClusterStateService {
136136
private final String nodeId;
137137
private final NamedXContentRegistry namedXContentRegistry;
138138
private final BigArrays bigArrays;
139-
private final boolean preserveUnknownCustoms;
140139
private final LongSupplier relativeTimeMillisSupplier;
141140

142141
private volatile TimeValue slowWriteLoggingThreshold;
143142

144143
public PersistedClusterStateService(NodeEnvironment nodeEnvironment, NamedXContentRegistry namedXContentRegistry, BigArrays bigArrays,
145144
ClusterSettings clusterSettings, LongSupplier relativeTimeMillisSupplier) {
146145
this(nodeEnvironment.nodeDataPaths(), nodeEnvironment.nodeId(), namedXContentRegistry, bigArrays, clusterSettings,
147-
relativeTimeMillisSupplier, false);
146+
relativeTimeMillisSupplier);
148147
}
149148

150149
public PersistedClusterStateService(Path[] dataPaths, String nodeId, NamedXContentRegistry namedXContentRegistry, BigArrays bigArrays,
151-
ClusterSettings clusterSettings, LongSupplier relativeTimeMillisSupplier,
152-
boolean preserveUnknownCustoms) {
150+
ClusterSettings clusterSettings, LongSupplier relativeTimeMillisSupplier) {
153151
this.dataPaths = dataPaths;
154152
this.nodeId = nodeId;
155153
this.namedXContentRegistry = namedXContentRegistry;
156154
this.bigArrays = bigArrays;
157155
this.relativeTimeMillisSupplier = relativeTimeMillisSupplier;
158-
this.preserveUnknownCustoms = preserveUnknownCustoms;
159156
this.slowWriteLoggingThreshold = clusterSettings.get(SLOW_WRITE_LOGGING_THRESHOLD);
160157
clusterSettings.addSettingsUpdateConsumer(SLOW_WRITE_LOGGING_THRESHOLD, this::setSlowWriteLoggingThreshold);
161158
}
@@ -383,8 +380,7 @@ private OnDiskState loadOnDiskState(Path dataPath, DirectoryReader reader) throw
383380
consumeFromType(searcher, GLOBAL_TYPE_NAME, bytes ->
384381
{
385382
final MetaData metaData = MetaData.Builder.fromXContent(XContentFactory.xContent(XContentType.SMILE)
386-
.createParser(namedXContentRegistry, LoggingDeprecationHandler.INSTANCE, bytes.bytes, bytes.offset, bytes.length),
387-
preserveUnknownCustoms);
383+
.createParser(namedXContentRegistry, LoggingDeprecationHandler.INSTANCE, bytes.bytes, bytes.offset, bytes.length));
388384
logger.trace("found global metadata with last-accepted term [{}]", metaData.coordinationMetaData().term());
389385
if (builderReference.get() != null) {
390386
throw new IllegalStateException("duplicate global metadata found in [" + dataPath + "]");

0 commit comments

Comments
 (0)