-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Operator/role mapping #89667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Operator/role mapping #89667
Changes from all commits
cd4e5ee
4723726
2f79c83
3cfaa82
e7736b1
e67636d
39803d4
aa38aa8
3ecd25b
ebecdeb
6fcf396
524572b
005e8f2
1791ebf
00384cb
bdcec01
4059718
87b6a69
7835d4e
b5b1d52
f718fcc
21d84c2
b2955a9
8baa259
e327472
f03a67b
5d947a6
ffacba6
390bd1b
83cbdd4
b8d52a6
48094e2
df7f76c
3889f03
c99d0c3
04046d5
9f8b4bc
caeb188
1f2ce32
af2a181
f2afc9f
fb906d2
796e61d
1964bf7
d2af580
d41cd20
e988bde
5058f74
8ac3287
cea0668
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 89667 | ||
| summary: Operator/role mapping | ||
| area: Infra/Core | ||
| type: enhancement | ||
| issues: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * 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.action.support; | ||
|
|
||
| import org.elasticsearch.action.ActionListener; | ||
| import org.elasticsearch.action.ActionRequest; | ||
| import org.elasticsearch.action.ActionResponse; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.common.io.stream.Writeable; | ||
| import org.elasticsearch.reservedstate.ActionWithReservedState; | ||
| import org.elasticsearch.tasks.Task; | ||
| import org.elasticsearch.transport.TransportService; | ||
|
|
||
| /** | ||
| * An extension of the {@link HandledTransportAction} class, which wraps the doExecute call with a check for clashes | ||
| * with the reserved cluster state. | ||
| */ | ||
| public abstract class ReservedStateAwareHandledTransportAction<Request extends ActionRequest, Response extends ActionResponse> extends | ||
| HandledTransportAction<Request, Response> | ||
| implements | ||
| ActionWithReservedState<Request> { | ||
| private final ClusterService clusterService; | ||
|
|
||
| protected ReservedStateAwareHandledTransportAction( | ||
| String actionName, | ||
| ClusterService clusterService, | ||
| TransportService transportService, | ||
| ActionFilters actionFilters, | ||
| Writeable.Reader<Request> requestReader | ||
| ) { | ||
| super(actionName, transportService, actionFilters, requestReader); | ||
| this.clusterService = clusterService; | ||
| } | ||
|
|
||
| /** | ||
| * A doExecute method wrapped with a check for clashes with updates to the reserved cluster state | ||
| */ | ||
| protected abstract void doExecuteProtected(Task task, Request request, ActionListener<Response> listener); | ||
|
|
||
| @Override | ||
| protected void doExecute(Task task, Request request, ActionListener<Response> listener) { | ||
| assert reservedStateHandlerName().isPresent(); | ||
|
|
||
| validateForReservedState(clusterService.state(), reservedStateHandlerName().get(), modifiedKeys(request), request.toString()); | ||
| doExecuteProtected(task, request, listener); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,6 @@ | |
| import org.elasticsearch.cluster.block.ClusterBlockException; | ||
| import org.elasticsearch.cluster.block.ClusterBlockLevel; | ||
| import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; | ||
| import org.elasticsearch.cluster.metadata.ReservedStateMetadata; | ||
| import org.elasticsearch.cluster.node.DiscoveryNode; | ||
| import org.elasticsearch.cluster.node.DiscoveryNodes; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
|
|
@@ -34,7 +33,7 @@ | |
| import org.elasticsearch.gateway.GatewayService; | ||
| import org.elasticsearch.index.IndexNotFoundException; | ||
| import org.elasticsearch.node.NodeClosedException; | ||
| import org.elasticsearch.reservedstate.ReservedClusterStateHandler; | ||
| import org.elasticsearch.reservedstate.ActionWithReservedState; | ||
| import org.elasticsearch.tasks.CancellableTask; | ||
| import org.elasticsearch.tasks.Task; | ||
| import org.elasticsearch.tasks.TaskCancelledException; | ||
|
|
@@ -44,11 +43,7 @@ | |
| import org.elasticsearch.transport.TransportException; | ||
| import org.elasticsearch.transport.TransportService; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import java.util.function.Predicate; | ||
|
|
||
| import static org.elasticsearch.core.Strings.format; | ||
|
|
@@ -57,7 +52,9 @@ | |
| * A base class for operations that needs to be performed on the master node. | ||
| */ | ||
| public abstract class TransportMasterNodeAction<Request extends MasterNodeRequest<Request>, Response extends ActionResponse> extends | ||
| HandledTransportAction<Request, Response> { | ||
| HandledTransportAction<Request, Response> | ||
| implements | ||
| ActionWithReservedState<Request> { | ||
|
|
||
| private static final Logger logger = LogManager.getLogger(TransportMasterNodeAction.class); | ||
|
|
||
|
|
@@ -149,65 +146,24 @@ private ClusterBlockException checkBlockIfStateRecovered(Request request, Cluste | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Override this method if the master node action also has an {@link ReservedClusterStateHandler} | ||
| * interaction. | ||
| * <p> | ||
| * We need to check if certain settings or entities are allowed to be modified by the master node | ||
| * action, depending on if they are set as reserved in 'operator' mode (file based settings, modules, plugins). | ||
| * | ||
| * @return an Optional of the {@link ReservedClusterStateHandler} name | ||
| */ | ||
| protected Optional<String> reservedStateHandlerName() { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| /** | ||
| * Override this method to return the keys of the cluster state or cluster entities that are modified by | ||
| * the Request object. | ||
| * <p> | ||
| * This method is used by the reserved state handler logic (see {@link ReservedClusterStateHandler}) | ||
| * to verify if the keys don't conflict with an existing key set as reserved. | ||
| * | ||
| * @param request the TransportMasterNode request | ||
| * @return set of String keys intended to be modified/set/deleted by this request | ||
| */ | ||
| protected Set<String> modifiedKeys(Request request) { | ||
| return Collections.emptySet(); | ||
| } | ||
|
|
||
| // package private for testing | ||
| void validateForImmutableState(Request request, ClusterState state) { | ||
| void validateForReservedState(Request request, ClusterState state) { | ||
| Optional<String> handlerName = reservedStateHandlerName(); | ||
| assert handlerName.isPresent(); | ||
|
|
||
| Set<String> modified = modifiedKeys(request); | ||
| List<String> errors = new ArrayList<>(); | ||
|
|
||
| for (ReservedStateMetadata metadata : state.metadata().reservedStateMetadata().values()) { | ||
| Set<String> conflicts = metadata.conflicts(handlerName.get(), modified); | ||
| if (conflicts.isEmpty() == false) { | ||
| errors.add(format("[%s] set as read-only by [%s]", String.join(", ", conflicts), metadata.namespace())); | ||
| } | ||
| } | ||
|
|
||
| if (errors.isEmpty() == false) { | ||
| throw new IllegalArgumentException( | ||
| format("Failed to process request [%s] with errors: [%s]", request, String.join(", ", errors)) | ||
| ); | ||
| } | ||
| validateForReservedState(state, handlerName.get(), modifiedKeys(request), request.toString()); | ||
| } | ||
|
|
||
| // package private for testing | ||
| boolean supportsImmutableState() { | ||
| boolean supportsReservedState() { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are simple renames, which were somehow missed before. We did rename from immutable to reserved. |
||
| return reservedStateHandlerName().isPresent(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void doExecute(Task task, final Request request, ActionListener<Response> listener) { | ||
| ClusterState state = clusterService.state(); | ||
| if (supportsImmutableState()) { | ||
| validateForImmutableState(request, state); | ||
| if (supportsReservedState()) { | ||
| validateForReservedState(request, state); | ||
| } | ||
| logger.trace("starting processing request [{}] with cluster state version [{}]", request, state.version()); | ||
| if (task != null) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -283,6 +283,13 @@ public Builder putHandler(ReservedStateHandlerMetadata handler) { | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the current handler metadata stored in the builder | ||
| */ | ||
| public ReservedStateHandlerMetadata getHandler(String handlerName) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: maybe this is better named
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll address this in a followup, it will increase the size of this PR because the method is used in many tests. |
||
| return this.handlers.get(handlerName); | ||
| } | ||
|
|
||
| /** | ||
| * Builds an {@link ReservedStateMetadata} from this builder. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| /* | ||
| * 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.reservedstate; | ||
|
|
||
| import org.elasticsearch.cluster.ClusterState; | ||
| import org.elasticsearch.cluster.metadata.ReservedStateMetadata; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
|
|
||
| import static org.elasticsearch.core.Strings.format; | ||
|
|
||
| public interface ActionWithReservedState<T> { | ||
| /** | ||
| * Override this method if the master node action also has an {@link ReservedClusterStateHandler} | ||
| * interaction. | ||
| * <p> | ||
| * We need to check if certain settings or entities are allowed to be modified by the master node | ||
| * action, depending on if they are set as reserved in 'operator' mode (file based settings, modules, plugins). | ||
| * | ||
| * @return an Optional of the {@link ReservedClusterStateHandler} name | ||
| */ | ||
| default Optional<String> reservedStateHandlerName() { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| /** | ||
| * Override this method to return the keys of the cluster state or cluster entities that are modified by | ||
| * the Request object. | ||
| * <p> | ||
| * This method is used by the reserved state handler logic (see {@link ReservedClusterStateHandler}) | ||
| * to verify if the keys don't conflict with an existing key set as reserved. | ||
| * | ||
| * @param request the TransportMasterNode request | ||
| * @return set of String keys intended to be modified/set/deleted by this request | ||
| */ | ||
| default Set<String> modifiedKeys(T request) { | ||
| return Collections.emptySet(); | ||
| } | ||
|
|
||
| /** | ||
| * Helper method that verifies for key clashes on reserved state updates | ||
| * @param state the current cluster state | ||
| * @param handlerName the name of the reserved state handler related to this implementation | ||
| * @param modified the set of modified keys by the related request | ||
| * @param request a string representation of the request for error reporting purposes | ||
| */ | ||
| default void validateForReservedState(ClusterState state, String handlerName, Set<String> modified, String request) { | ||
| List<String> errors = new ArrayList<>(); | ||
|
|
||
| for (ReservedStateMetadata metadata : state.metadata().reservedStateMetadata().values()) { | ||
| Set<String> conflicts = metadata.conflicts(handlerName, modified); | ||
| if (conflicts.isEmpty() == false) { | ||
| errors.add(format("[%s] set as read-only by [%s]", String.join(", ", conflicts), metadata.namespace())); | ||
| } | ||
| } | ||
|
|
||
| if (errors.isEmpty() == false) { | ||
| throw new IllegalArgumentException( | ||
| format("Failed to process request [%s] with errors: [%s]", request, String.join(", ", errors)) | ||
| ); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /* | ||
| * 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.reservedstate; | ||
|
|
||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * A wrapper class for notifying listeners on non cluster state transformation operation completion. | ||
| * <p> | ||
| * Certain {@link ReservedClusterStateHandler} implementations may need to perform additional | ||
| * operations other than modifying the cluster state. This can range from cache | ||
| * invalidation to implementing state handlers that do not write to the cluster state, e.g. role mappings. | ||
| * These additional transformation steps are implemented as separate async operation after the validation of | ||
| * the cluster state update steps (trial run in {@link org.elasticsearch.reservedstate.service.ReservedClusterStateService}). | ||
| */ | ||
| public record NonStateTransformResult(String handlerName, Set<String> updatedKeys) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the role mappings don't use TransportMasterNodeAction (which is cluster state specific) we add this small wrapper class to ensure the reserved state information is checked before allowing REST actions to create/update/delete role mappings.