Skip to content

Commit 2b3dbde

Browse files
grcevskiywangd
andauthored
Operator/role mapping (#89667)
Implement file based settings handler for role mappings. Co-authored-by: Yang Wang <[email protected]>
1 parent 289533b commit 2b3dbde

File tree

37 files changed

+1882
-137
lines changed

37 files changed

+1882
-137
lines changed

docs/changelog/89667.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 89667
2+
summary: Operator/role mapping
3+
area: Infra/Core
4+
type: enhancement
5+
issues: []

server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/delete/TransportDeleteRepositoryAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ protected void masterOperation(
7575
}
7676

7777
@Override
78-
protected Optional<String> reservedStateHandlerName() {
78+
public Optional<String> reservedStateHandlerName() {
7979
return Optional.of(ReservedRepositoryAction.NAME);
8080
}
8181

8282
@Override
83-
protected Set<String> modifiedKeys(DeleteRepositoryRequest request) {
83+
public Set<String> modifiedKeys(DeleteRepositoryRequest request) {
8484
return Set.of(request.name());
8585
}
8686
}

server/src/main/java/org/elasticsearch/action/admin/cluster/repositories/put/TransportPutRepositoryAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ protected void masterOperation(
7272
}
7373

7474
@Override
75-
protected Optional<String> reservedStateHandlerName() {
75+
public Optional<String> reservedStateHandlerName() {
7676
return Optional.of(ReservedRepositoryAction.NAME);
7777
}
7878

7979
@Override
80-
protected Set<String> modifiedKeys(PutRepositoryRequest request) {
80+
public Set<String> modifiedKeys(PutRepositoryRequest request) {
8181
return Set.of(request.name());
8282
}
8383
}

server/src/main/java/org/elasticsearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ private static boolean checkClearedBlockAndArchivedSettings(
131131
}
132132

133133
@Override
134-
protected Optional<String> reservedStateHandlerName() {
134+
public Optional<String> reservedStateHandlerName() {
135135
return Optional.of(ReservedClusterSettingsAction.NAME);
136136
}
137137

138138
@Override
139-
protected Set<String> modifiedKeys(ClusterUpdateSettingsRequest request) {
139+
public Set<String> modifiedKeys(ClusterUpdateSettingsRequest request) {
140140
Settings allSettings = Settings.builder().put(request.persistentSettings()).put(request.transientSettings()).build();
141141
return allSettings.keySet();
142142
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.action.support;
10+
11+
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.action.ActionRequest;
13+
import org.elasticsearch.action.ActionResponse;
14+
import org.elasticsearch.cluster.service.ClusterService;
15+
import org.elasticsearch.common.io.stream.Writeable;
16+
import org.elasticsearch.reservedstate.ActionWithReservedState;
17+
import org.elasticsearch.tasks.Task;
18+
import org.elasticsearch.transport.TransportService;
19+
20+
/**
21+
* An extension of the {@link HandledTransportAction} class, which wraps the doExecute call with a check for clashes
22+
* with the reserved cluster state.
23+
*/
24+
public abstract class ReservedStateAwareHandledTransportAction<Request extends ActionRequest, Response extends ActionResponse> extends
25+
HandledTransportAction<Request, Response>
26+
implements
27+
ActionWithReservedState<Request> {
28+
private final ClusterService clusterService;
29+
30+
protected ReservedStateAwareHandledTransportAction(
31+
String actionName,
32+
ClusterService clusterService,
33+
TransportService transportService,
34+
ActionFilters actionFilters,
35+
Writeable.Reader<Request> requestReader
36+
) {
37+
super(actionName, transportService, actionFilters, requestReader);
38+
this.clusterService = clusterService;
39+
}
40+
41+
/**
42+
* A doExecute method wrapped with a check for clashes with updates to the reserved cluster state
43+
*/
44+
protected abstract void doExecuteProtected(Task task, Request request, ActionListener<Response> listener);
45+
46+
@Override
47+
protected void doExecute(Task task, Request request, ActionListener<Response> listener) {
48+
assert reservedStateHandlerName().isPresent();
49+
50+
validateForReservedState(clusterService.state(), reservedStateHandlerName().get(), modifiedKeys(request), request.toString());
51+
doExecuteProtected(task, request, listener);
52+
}
53+
}

server/src/main/java/org/elasticsearch/action/support/master/TransportMasterNodeAction.java

Lines changed: 9 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.elasticsearch.cluster.block.ClusterBlockException;
2424
import org.elasticsearch.cluster.block.ClusterBlockLevel;
2525
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
26-
import org.elasticsearch.cluster.metadata.ReservedStateMetadata;
2726
import org.elasticsearch.cluster.node.DiscoveryNode;
2827
import org.elasticsearch.cluster.node.DiscoveryNodes;
2928
import org.elasticsearch.cluster.service.ClusterService;
@@ -34,7 +33,7 @@
3433
import org.elasticsearch.gateway.GatewayService;
3534
import org.elasticsearch.index.IndexNotFoundException;
3635
import org.elasticsearch.node.NodeClosedException;
37-
import org.elasticsearch.reservedstate.ReservedClusterStateHandler;
36+
import org.elasticsearch.reservedstate.ActionWithReservedState;
3837
import org.elasticsearch.tasks.CancellableTask;
3938
import org.elasticsearch.tasks.Task;
4039
import org.elasticsearch.tasks.TaskCancelledException;
@@ -44,11 +43,7 @@
4443
import org.elasticsearch.transport.TransportException;
4544
import org.elasticsearch.transport.TransportService;
4645

47-
import java.util.ArrayList;
48-
import java.util.Collections;
49-
import java.util.List;
5046
import java.util.Optional;
51-
import java.util.Set;
5247
import java.util.function.Predicate;
5348

5449
import static org.elasticsearch.core.Strings.format;
@@ -57,7 +52,9 @@
5752
* A base class for operations that needs to be performed on the master node.
5853
*/
5954
public abstract class TransportMasterNodeAction<Request extends MasterNodeRequest<Request>, Response extends ActionResponse> extends
60-
HandledTransportAction<Request, Response> {
55+
HandledTransportAction<Request, Response>
56+
implements
57+
ActionWithReservedState<Request> {
6158

6259
private static final Logger logger = LogManager.getLogger(TransportMasterNodeAction.class);
6360

@@ -149,65 +146,24 @@ private ClusterBlockException checkBlockIfStateRecovered(Request request, Cluste
149146
}
150147
}
151148

152-
/**
153-
* Override this method if the master node action also has an {@link ReservedClusterStateHandler}
154-
* interaction.
155-
* <p>
156-
* We need to check if certain settings or entities are allowed to be modified by the master node
157-
* action, depending on if they are set as reserved in 'operator' mode (file based settings, modules, plugins).
158-
*
159-
* @return an Optional of the {@link ReservedClusterStateHandler} name
160-
*/
161-
protected Optional<String> reservedStateHandlerName() {
162-
return Optional.empty();
163-
}
164-
165-
/**
166-
* Override this method to return the keys of the cluster state or cluster entities that are modified by
167-
* the Request object.
168-
* <p>
169-
* This method is used by the reserved state handler logic (see {@link ReservedClusterStateHandler})
170-
* to verify if the keys don't conflict with an existing key set as reserved.
171-
*
172-
* @param request the TransportMasterNode request
173-
* @return set of String keys intended to be modified/set/deleted by this request
174-
*/
175-
protected Set<String> modifiedKeys(Request request) {
176-
return Collections.emptySet();
177-
}
178-
179149
// package private for testing
180-
void validateForImmutableState(Request request, ClusterState state) {
150+
void validateForReservedState(Request request, ClusterState state) {
181151
Optional<String> handlerName = reservedStateHandlerName();
182152
assert handlerName.isPresent();
183153

184-
Set<String> modified = modifiedKeys(request);
185-
List<String> errors = new ArrayList<>();
186-
187-
for (ReservedStateMetadata metadata : state.metadata().reservedStateMetadata().values()) {
188-
Set<String> conflicts = metadata.conflicts(handlerName.get(), modified);
189-
if (conflicts.isEmpty() == false) {
190-
errors.add(format("[%s] set as read-only by [%s]", String.join(", ", conflicts), metadata.namespace()));
191-
}
192-
}
193-
194-
if (errors.isEmpty() == false) {
195-
throw new IllegalArgumentException(
196-
format("Failed to process request [%s] with errors: [%s]", request, String.join(", ", errors))
197-
);
198-
}
154+
validateForReservedState(state, handlerName.get(), modifiedKeys(request), request.toString());
199155
}
200156

201157
// package private for testing
202-
boolean supportsImmutableState() {
158+
boolean supportsReservedState() {
203159
return reservedStateHandlerName().isPresent();
204160
}
205161

206162
@Override
207163
protected void doExecute(Task task, final Request request, ActionListener<Response> listener) {
208164
ClusterState state = clusterService.state();
209-
if (supportsImmutableState()) {
210-
validateForImmutableState(request, state);
165+
if (supportsReservedState()) {
166+
validateForReservedState(request, state);
211167
}
212168
logger.trace("starting processing request [{}] with cluster state version [{}]", request, state.version());
213169
if (task != null) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,13 @@ public Builder putHandler(ReservedStateHandlerMetadata handler) {
283283
return this;
284284
}
285285

286+
/**
287+
* Returns the current handler metadata stored in the builder
288+
*/
289+
public ReservedStateHandlerMetadata getHandler(String handlerName) {
290+
return this.handlers.get(handlerName);
291+
}
292+
286293
/**
287294
* Builds an {@link ReservedStateMetadata} from this builder.
288295
*
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.reservedstate;
10+
11+
import org.elasticsearch.cluster.ClusterState;
12+
import org.elasticsearch.cluster.metadata.ReservedStateMetadata;
13+
14+
import java.util.ArrayList;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import java.util.Set;
19+
20+
import static org.elasticsearch.core.Strings.format;
21+
22+
public interface ActionWithReservedState<T> {
23+
/**
24+
* Override this method if the master node action also has an {@link ReservedClusterStateHandler}
25+
* interaction.
26+
* <p>
27+
* We need to check if certain settings or entities are allowed to be modified by the master node
28+
* action, depending on if they are set as reserved in 'operator' mode (file based settings, modules, plugins).
29+
*
30+
* @return an Optional of the {@link ReservedClusterStateHandler} name
31+
*/
32+
default Optional<String> reservedStateHandlerName() {
33+
return Optional.empty();
34+
}
35+
36+
/**
37+
* Override this method to return the keys of the cluster state or cluster entities that are modified by
38+
* the Request object.
39+
* <p>
40+
* This method is used by the reserved state handler logic (see {@link ReservedClusterStateHandler})
41+
* to verify if the keys don't conflict with an existing key set as reserved.
42+
*
43+
* @param request the TransportMasterNode request
44+
* @return set of String keys intended to be modified/set/deleted by this request
45+
*/
46+
default Set<String> modifiedKeys(T request) {
47+
return Collections.emptySet();
48+
}
49+
50+
/**
51+
* Helper method that verifies for key clashes on reserved state updates
52+
* @param state the current cluster state
53+
* @param handlerName the name of the reserved state handler related to this implementation
54+
* @param modified the set of modified keys by the related request
55+
* @param request a string representation of the request for error reporting purposes
56+
*/
57+
default void validateForReservedState(ClusterState state, String handlerName, Set<String> modified, String request) {
58+
List<String> errors = new ArrayList<>();
59+
60+
for (ReservedStateMetadata metadata : state.metadata().reservedStateMetadata().values()) {
61+
Set<String> conflicts = metadata.conflicts(handlerName, modified);
62+
if (conflicts.isEmpty() == false) {
63+
errors.add(format("[%s] set as read-only by [%s]", String.join(", ", conflicts), metadata.namespace()));
64+
}
65+
}
66+
67+
if (errors.isEmpty() == false) {
68+
throw new IllegalArgumentException(
69+
format("Failed to process request [%s] with errors: [%s]", request, String.join(", ", errors))
70+
);
71+
}
72+
}
73+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.reservedstate;
10+
11+
import java.util.Set;
12+
13+
/**
14+
* A wrapper class for notifying listeners on non cluster state transformation operation completion.
15+
* <p>
16+
* Certain {@link ReservedClusterStateHandler} implementations may need to perform additional
17+
* operations other than modifying the cluster state. This can range from cache
18+
* invalidation to implementing state handlers that do not write to the cluster state, e.g. role mappings.
19+
* These additional transformation steps are implemented as separate async operation after the validation of
20+
* the cluster state update steps (trial run in {@link org.elasticsearch.reservedstate.service.ReservedClusterStateService}).
21+
*/
22+
public record NonStateTransformResult(String handlerName, Set<String> updatedKeys) {}

server/src/main/java/org/elasticsearch/reservedstate/TransformState.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88

99
package org.elasticsearch.reservedstate;
1010

11+
import org.elasticsearch.action.ActionListener;
1112
import org.elasticsearch.cluster.ClusterState;
1213

1314
import java.util.Set;
15+
import java.util.function.Consumer;
1416

1517
/**
1618
* A {@link ClusterState} wrapper used by the ReservedClusterStateService to pass the
1719
* current state as well as previous keys set by an {@link ReservedClusterStateHandler} to each transform
1820
* step of the cluster state update.
1921
*
22+
* Each {@link ReservedClusterStateHandler} can also provide a non cluster state transform consumer that should run after
23+
* the cluster state is fully validated. This allows for handlers to perform extra steps, like clearing caches or saving
24+
* other state outside the cluster state. The consumer, if provided, must return a {@link NonStateTransformResult} with
25+
* the keys that will be saved as reserved in the cluster state.
2026
*/
21-
public record TransformState(ClusterState state, Set<String> keys) {}
27+
public record TransformState(ClusterState state, Set<String> keys, Consumer<ActionListener<NonStateTransformResult>> nonStateTransform) {
28+
public TransformState(ClusterState state, Set<String> keys) {
29+
this(state, keys, null);
30+
}
31+
}

0 commit comments

Comments
 (0)