-
Notifications
You must be signed in to change notification settings - Fork 24.3k
/
ReservedStateMetadata.java
304 lines (273 loc) · 11.8 KB
/
ReservedStateMetadata.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.SimpleDiffable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.reservedstate.ReservedClusterStateHandler;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentFragment;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Metadata class that contains information about reserved cluster state set
* through file based settings or by modules/plugins.
*
* <p>
* These types of cluster settings/entities can be read through the REST API,
* but can only be modified through a versioned 'operator mode' update, e.g.
* file based settings or module/plugin upgrade.
*/
public record ReservedStateMetadata(
String namespace,
Long version,
Map<String, ReservedStateHandlerMetadata> handlers,
ReservedStateErrorMetadata errorMetadata
) implements SimpleDiffable<ReservedStateMetadata>, ToXContentFragment {
public static final Long NO_VERSION = Long.MIN_VALUE; // use min long as sentinel for uninitialized version
private static final ParseField VERSION = new ParseField("version");
private static final ParseField HANDLERS = new ParseField("handlers");
private static final ParseField ERRORS_METADATA = new ParseField("errors");
/**
* ReservedStateMetadata contains information about reserved cluster settings.
*
* <p>
* These settings cannot be updated by the end user and are set outside of the
* REST layer, e.g. through file based settings or by plugin/modules.
*
* @param namespace The namespace of the setting creator, e.g. file_settings, security plugin, etc.
* @param version The update version, must increase with each update
* @param handlers Per state update handler information on key set in by this update. These keys are validated at REST time.
* @param errorMetadata If the update failed for some reason, this is where we store the error information metadata.
*/
public ReservedStateMetadata {}
/**
* Creates a set intersection between cluster state keys set by a given {@link ReservedClusterStateHandler}
* and the input set.
*
* <p>
* This method is to be used to check if a REST action handler is allowed to modify certain cluster state.
*
* @param handlerName the name of the reserved state handler we need to check for keys
* @param modified a set of keys we want to see if we can modify.
* @return
*/
public Set<String> conflicts(String handlerName, Set<String> modified) {
ReservedStateHandlerMetadata handlerMetadata = handlers.get(handlerName);
if (handlerMetadata == null || handlerMetadata.keys().isEmpty()) {
return Collections.emptySet();
}
Set<String> intersect = new HashSet<>(handlerMetadata.keys());
intersect.retainAll(modified);
return Collections.unmodifiableSet(intersect);
}
/**
* Reads an {@link ReservedStateMetadata} from a {@link StreamInput}
*
* @param in the {@link StreamInput} to read from
* @return {@link ReservedStateMetadata}
* @throws IOException
*/
public static ReservedStateMetadata readFrom(StreamInput in) throws IOException {
Builder builder = new Builder(in.readString()).version(in.readLong());
int handlersSize = in.readVInt();
for (int i = 0; i < handlersSize; i++) {
ReservedStateHandlerMetadata handler = ReservedStateHandlerMetadata.readFrom(in);
builder.putHandler(handler);
}
builder.errorMetadata(in.readOptionalWriteable(ReservedStateErrorMetadata::readFrom));
return builder.build();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(namespace);
out.writeLong(version);
out.writeCollection(handlers.values());
out.writeOptionalWriteable(errorMetadata);
}
/**
* Reads an {@link ReservedStateMetadata} {@link Diff} from {@link StreamInput}
*
* @param in the {@link StreamInput} to read the diff from
* @return a {@link Diff} of {@link ReservedStateMetadata}
* @throws IOException
*/
public static Diff<ReservedStateMetadata> readDiffFrom(StreamInput in) throws IOException {
return SimpleDiffable.readDiffFrom(ReservedStateMetadata::readFrom, in);
}
/**
* Convenience method for creating a {@link Builder} for {@link ReservedStateMetadata}
*
* @param namespace the namespace under which we'll store the {@link ReservedStateMetadata}
* @return {@link Builder}
*/
public static Builder builder(String namespace) {
return new Builder(namespace);
}
/**
* Convenience method for creating a {@link Builder} for {@link ReservedStateMetadata}
*
* @param namespace the namespace under which we'll store the {@link ReservedStateMetadata}
* @param metadata an existing {@link ReservedStateMetadata}
* @return {@link Builder}
*/
public static Builder builder(String namespace, ReservedStateMetadata metadata) {
return new Builder(namespace, metadata);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(namespace());
builder.field(VERSION.getPreferredName(), version);
builder.startObject(HANDLERS.getPreferredName());
for (var i = handlers.entrySet().stream().sorted(Map.Entry.comparingByKey()).iterator(); i.hasNext();) {
i.next().getValue().toXContent(builder, params);
}
builder.endObject();
builder.field(ERRORS_METADATA.getPreferredName(), errorMetadata);
builder.endObject();
return builder;
}
private static final ConstructingObjectParser<ReservedStateMetadata, String> PARSER = new ConstructingObjectParser<>(
"reserved_state_metadata",
false,
(a, namespace) -> {
Map<String, ReservedStateHandlerMetadata> handlers = new HashMap<>();
@SuppressWarnings("unchecked")
List<ReservedStateHandlerMetadata> handlersList = (List<ReservedStateHandlerMetadata>) a[1];
handlersList.forEach(h -> handlers.put(h.name(), h));
return new ReservedStateMetadata(namespace, (Long) a[0], Map.copyOf(handlers), (ReservedStateErrorMetadata) a[2]);
}
);
static {
PARSER.declareLong(constructorArg(), VERSION);
PARSER.declareNamedObjects(optionalConstructorArg(), (p, c, name) -> ReservedStateHandlerMetadata.fromXContent(p, name), HANDLERS);
PARSER.declareObjectOrNull(optionalConstructorArg(), (p, c) -> ReservedStateErrorMetadata.fromXContent(p), null, ERRORS_METADATA);
}
/**
* Reads {@link ReservedStateMetadata} from {@link XContentParser}
*
* @param parser {@link XContentParser}
* @return {@link ReservedStateMetadata}
* @throws IOException
*/
public static ReservedStateMetadata fromXContent(final XContentParser parser) throws IOException {
parser.nextToken();
return PARSER.apply(parser, parser.currentName());
}
/**
* Builder class for {@link ReservedStateMetadata}
*/
public static class Builder {
private final String namespace;
private Long version;
private Map<String, ReservedStateHandlerMetadata> handlers;
ReservedStateErrorMetadata errorMetadata;
/**
* Empty builder for ReservedStateMetadata.
* <p>
* The reserved metadata namespace is a required parameter
*
* @param namespace The namespace for this reserved metadata
*/
public Builder(String namespace) {
this.namespace = namespace;
this.version = NO_VERSION;
this.handlers = new HashMap<>();
this.errorMetadata = null;
}
/**
* Creates an reserved state metadata builder
*
* @param metadata the previous metadata
*/
public Builder(ReservedStateMetadata metadata) {
this(metadata.namespace);
this.version = metadata.version;
this.handlers = new HashMap<>(metadata.handlers);
this.errorMetadata = metadata.errorMetadata;
}
/**
* Creates an reserved state metadata builder
*
* @param namespace the namespace for which we are storing metadata, e.g. file_settings
* @param metadata the previous metadata
*/
public Builder(String namespace, ReservedStateMetadata metadata) {
this(namespace);
if (metadata != null) {
this.version = metadata.version;
this.handlers = new HashMap<>(metadata.handlers);
this.errorMetadata = metadata.errorMetadata;
}
}
/**
* Stores the version for the reserved state metadata.
*
* <p>
* Each new reserved cluster state update mode requires a version bump.
* The version increase doesn't have to be monotonic.
*
* @param version the new reserved state metadata version
* @return {@link Builder}
*/
public Builder version(Long version) {
this.version = version;
return this;
}
/**
* Adds {@link ReservedStateErrorMetadata} if we need to store error information about certain
* reserved state processing.
*
* @param errorMetadata {@link ReservedStateErrorMetadata}
* @return {@link Builder}
*/
public Builder errorMetadata(ReservedStateErrorMetadata errorMetadata) {
this.errorMetadata = errorMetadata;
return this;
}
/**
* Adds an {@link ReservedStateHandlerMetadata} for this {@link ReservedStateMetadata}.
*
* <p>
* The handler metadata is stored in a map, keyed off the {@link ReservedStateHandlerMetadata} name. Previously
* stored {@link ReservedStateHandlerMetadata} for a given name is overwritten.
*
* @param handler {@link ReservedStateHandlerMetadata}
* @return {@link Builder}
*/
public Builder putHandler(ReservedStateHandlerMetadata handler) {
this.handlers.put(handler.name(), handler);
return this;
}
/**
* Returns the current handler metadata stored in the builder
*/
public ReservedStateHandlerMetadata getHandler(String handlerName) {
return this.handlers.get(handlerName);
}
/**
* Builds an {@link ReservedStateMetadata} from this builder.
*
* @return {@link ReservedStateMetadata}
*/
public ReservedStateMetadata build() {
return new ReservedStateMetadata(namespace, version, Collections.unmodifiableMap(handlers), errorMetadata);
}
}
}