Skip to content

Commit fc93f77

Browse files
authored
Implement ILM/settings operator handlers (#88097)
Relates to #86224
1 parent e21c597 commit fc93f77

File tree

15 files changed

+753
-4
lines changed

15 files changed

+753
-4
lines changed

server/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,4 +358,6 @@
358358
with
359359
org.elasticsearch.cluster.coordination.NodeToolCliProvider,
360360
org.elasticsearch.index.shard.ShardToolCliProvider;
361+
362+
uses org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider;
361363
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929
import org.elasticsearch.common.settings.ClusterSettings;
3030
import org.elasticsearch.common.settings.Settings;
3131
import org.elasticsearch.core.SuppressForbidden;
32+
import org.elasticsearch.immutablestate.action.ImmutableClusterSettingsAction;
3233
import org.elasticsearch.tasks.Task;
3334
import org.elasticsearch.threadpool.ThreadPool;
3435
import org.elasticsearch.transport.TransportService;
3536

3637
import java.util.HashSet;
38+
import java.util.Optional;
3739
import java.util.Set;
3840

3941
import static org.elasticsearch.common.settings.AbstractScopedSettings.ARCHIVED_SETTINGS_PREFIX;
@@ -128,6 +130,17 @@ private static boolean checkClearedBlockAndArchivedSettings(
128130
return true;
129131
}
130132

133+
@Override
134+
protected Optional<String> immutableStateHandlerName() {
135+
return Optional.of(ImmutableClusterSettingsAction.NAME);
136+
}
137+
138+
@Override
139+
protected Set<String> modifiedKeys(ClusterUpdateSettingsRequest request) {
140+
Settings allSettings = Settings.builder().put(request.persistentSettings()).put(request.transientSettings()).build();
141+
return allSettings.keySet();
142+
}
143+
131144
private static final String UPDATE_TASK_SOURCE = "cluster_update_settings";
132145
private static final String REROUTE_TASK_SOURCE = "reroute_after_cluster_update_settings";
133146

@@ -243,6 +256,13 @@ public ClusterUpdateSettingsTask(
243256
this.request = request;
244257
}
245258

259+
/**
260+
* Used by the immutable state handler {@link ImmutableClusterSettingsAction}
261+
*/
262+
public ClusterUpdateSettingsTask(final ClusterSettings clusterSettings, ClusterUpdateSettingsRequest request) {
263+
this(clusterSettings, Priority.IMMEDIATE, request, null);
264+
}
265+
246266
@Override
247267
public ClusterState execute(final ClusterState currentState) {
248268
final ClusterState clusterState = updater.updateSettings(

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
import org.elasticsearch.transport.TransportException;
4343
import org.elasticsearch.transport.TransportService;
4444

45+
import java.util.Collections;
46+
import java.util.Optional;
47+
import java.util.Set;
4548
import java.util.function.Predicate;
4649

4750
import static org.elasticsearch.core.Strings.format;
@@ -142,6 +145,33 @@ private ClusterBlockException checkBlockIfStateRecovered(Request request, Cluste
142145
}
143146
}
144147

148+
/**
149+
* Override this method if the master node action also has an {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler}
150+
* interaction.
151+
* <p>
152+
* We need to check if certain settings or entities are allowed to be modified by the master node
153+
* action, depending on if they are set as immutable in 'operator' mode (file based settings, modules, plugins).
154+
*
155+
* @return an Optional of the {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler} name
156+
*/
157+
protected Optional<String> immutableStateHandlerName() {
158+
return Optional.empty();
159+
}
160+
161+
/**
162+
* Override this method to return the keys of the cluster state or cluster entities that are modified by
163+
* the Request object.
164+
* <p>
165+
* This method is used by the immutable state handler logic (see {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler})
166+
* to verify if the keys don't conflict with an existing key set as immutable.
167+
*
168+
* @param request the TransportMasterNode request
169+
* @return set of String keys intended to be modified/set/deleted by this request
170+
*/
171+
protected Set<String> modifiedKeys(Request request) {
172+
return Collections.emptySet();
173+
}
174+
145175
@Override
146176
protected void doExecute(Task task, final Request request, ActionListener<Response> listener) {
147177
ClusterState state = clusterService.state();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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.immutablestate.action;
10+
11+
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
12+
import org.elasticsearch.action.admin.cluster.settings.TransportClusterUpdateSettingsAction;
13+
import org.elasticsearch.client.internal.Requests;
14+
import org.elasticsearch.cluster.ClusterState;
15+
import org.elasticsearch.common.settings.ClusterSettings;
16+
import org.elasticsearch.immutablestate.ImmutableClusterStateHandler;
17+
import org.elasticsearch.immutablestate.TransformState;
18+
19+
import java.util.HashMap;
20+
import java.util.HashSet;
21+
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
25+
import static org.elasticsearch.common.util.Maps.asMap;
26+
27+
/**
28+
* This Action is the immutable state save version of RestClusterUpdateSettingsAction
29+
* <p>
30+
* It is used by the ImmutableClusterStateController to update the persistent cluster settings.
31+
* Since transient cluster settings are deprecated, this action doesn't support updating transient cluster settings.
32+
*/
33+
public class ImmutableClusterSettingsAction implements ImmutableClusterStateHandler<ClusterUpdateSettingsRequest> {
34+
35+
public static final String NAME = "cluster_settings";
36+
37+
private final ClusterSettings clusterSettings;
38+
39+
public ImmutableClusterSettingsAction(ClusterSettings clusterSettings) {
40+
this.clusterSettings = clusterSettings;
41+
}
42+
43+
@Override
44+
public String name() {
45+
return NAME;
46+
}
47+
48+
@SuppressWarnings("unchecked")
49+
private ClusterUpdateSettingsRequest prepare(Object input, Set<String> previouslySet) {
50+
final ClusterUpdateSettingsRequest clusterUpdateSettingsRequest = Requests.clusterUpdateSettingsRequest();
51+
52+
Map<String, ?> source = asMap(input);
53+
Map<String, Object> persistentSettings = new HashMap<>();
54+
Set<String> toDelete = new HashSet<>(previouslySet);
55+
56+
source.forEach((k, v) -> {
57+
persistentSettings.put(k, v);
58+
toDelete.remove(k);
59+
});
60+
61+
toDelete.forEach(k -> persistentSettings.put(k, null));
62+
63+
clusterUpdateSettingsRequest.persistentSettings(persistentSettings);
64+
return clusterUpdateSettingsRequest;
65+
}
66+
67+
@Override
68+
public TransformState transform(Object input, TransformState prevState) {
69+
ClusterUpdateSettingsRequest request = prepare(input, prevState.keys());
70+
71+
// allow empty requests, this is how we clean up settings
72+
if (request.persistentSettings().isEmpty() == false) {
73+
validate(request);
74+
}
75+
76+
ClusterState state = prevState.state();
77+
78+
TransportClusterUpdateSettingsAction.ClusterUpdateSettingsTask updateSettingsTask =
79+
new TransportClusterUpdateSettingsAction.ClusterUpdateSettingsTask(clusterSettings, request);
80+
81+
state = updateSettingsTask.execute(state);
82+
Set<String> currentKeys = request.persistentSettings()
83+
.keySet()
84+
.stream()
85+
.filter(k -> request.persistentSettings().hasValue(k))
86+
.collect(Collectors.toSet());
87+
88+
return new TransformState(state, currentKeys);
89+
}
90+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.immutablestate.action;
10+
11+
import org.elasticsearch.cluster.ClusterName;
12+
import org.elasticsearch.cluster.ClusterState;
13+
import org.elasticsearch.common.settings.ClusterSettings;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.immutablestate.TransformState;
16+
import org.elasticsearch.test.ESTestCase;
17+
import org.elasticsearch.xcontent.XContentParser;
18+
import org.elasticsearch.xcontent.XContentParserConfiguration;
19+
import org.elasticsearch.xcontent.XContentType;
20+
21+
import java.util.Collections;
22+
23+
import static org.hamcrest.Matchers.containsInAnyOrder;
24+
25+
public class ImmutableClusterSettingsActionTests extends ESTestCase {
26+
27+
private TransformState processJSON(ImmutableClusterSettingsAction action, TransformState prevState, String json) throws Exception {
28+
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, json)) {
29+
return action.transform(parser.map(), prevState);
30+
}
31+
}
32+
33+
public void testValidation() throws Exception {
34+
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
35+
36+
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
37+
TransformState prevState = new TransformState(state, Collections.emptySet());
38+
ImmutableClusterSettingsAction action = new ImmutableClusterSettingsAction(clusterSettings);
39+
40+
String badPolicyJSON = """
41+
{
42+
"indices.recovery.min_bytes_per_sec": "50mb"
43+
}""";
44+
45+
assertEquals(
46+
"persistent setting [indices.recovery.min_bytes_per_sec], not recognized",
47+
expectThrows(IllegalArgumentException.class, () -> processJSON(action, prevState, badPolicyJSON)).getMessage()
48+
);
49+
}
50+
51+
public void testSetUnsetSettings() throws Exception {
52+
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
53+
54+
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
55+
TransformState prevState = new TransformState(state, Collections.emptySet());
56+
ImmutableClusterSettingsAction action = new ImmutableClusterSettingsAction(clusterSettings);
57+
58+
String emptyJSON = "";
59+
60+
TransformState updatedState = processJSON(action, prevState, emptyJSON);
61+
assertEquals(0, updatedState.keys().size());
62+
assertEquals(prevState.state(), updatedState.state());
63+
64+
String settingsJSON = """
65+
{
66+
"indices.recovery.max_bytes_per_sec": "50mb",
67+
"cluster": {
68+
"remote": {
69+
"cluster_one": {
70+
"seeds": [
71+
"127.0.0.1:9300"
72+
]
73+
}
74+
}
75+
}
76+
}""";
77+
78+
prevState = updatedState;
79+
updatedState = processJSON(action, prevState, settingsJSON);
80+
assertThat(updatedState.keys(), containsInAnyOrder("indices.recovery.max_bytes_per_sec", "cluster.remote.cluster_one.seeds"));
81+
assertEquals("50mb", updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
82+
assertEquals("[127.0.0.1:9300]", updatedState.state().metadata().persistentSettings().get("cluster.remote.cluster_one.seeds"));
83+
84+
String oneSettingJSON = """
85+
{
86+
"indices.recovery.max_bytes_per_sec": "25mb"
87+
}""";
88+
89+
prevState = updatedState;
90+
updatedState = processJSON(action, prevState, oneSettingJSON);
91+
assertThat(updatedState.keys(), containsInAnyOrder("indices.recovery.max_bytes_per_sec"));
92+
assertEquals("25mb", updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
93+
assertNull(updatedState.state().metadata().persistentSettings().get("cluster.remote.cluster_one.seeds"));
94+
95+
prevState = updatedState;
96+
updatedState = processJSON(action, prevState, emptyJSON);
97+
assertEquals(0, updatedState.keys().size());
98+
assertNull(updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
99+
}
100+
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/action/DeleteLifecycleAction.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.IOException;
1919
import java.util.Objects;
2020

21+
import static org.elasticsearch.core.Strings.format;
22+
2123
public class DeleteLifecycleAction extends ActionType<AcknowledgedResponse> {
2224
public static final DeleteLifecycleAction INSTANCE = new DeleteLifecycleAction();
2325
public static final String NAME = "cluster:admin/ilm/delete";
@@ -75,6 +77,11 @@ public boolean equals(Object obj) {
7577
return Objects.equals(policyName, other.policyName);
7678
}
7779

80+
@Override
81+
public String toString() {
82+
return format("delete lifecycle policy [%s]", policyName);
83+
}
84+
7885
}
7986

8087
}

x-pack/plugin/ilm/src/main/java/module-info.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.elasticsearch.xpack.ilm.ILMImmutableStateHandlerProvider;
2+
13
/*
24
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
35
* or more contributor license agreements. Licensed under the Elastic License
@@ -16,4 +18,6 @@
1618
exports org.elasticsearch.xpack.ilm to org.elasticsearch.server;
1719
exports org.elasticsearch.xpack.slm.action to org.elasticsearch.server;
1820
exports org.elasticsearch.xpack.slm to org.elasticsearch.server;
21+
22+
provides org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider with ILMImmutableStateHandlerProvider;
1923
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.ilm;
9+
10+
import org.elasticsearch.immutablestate.ImmutableClusterStateHandler;
11+
import org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider;
12+
13+
import java.util.Arrays;
14+
import java.util.Collection;
15+
import java.util.Set;
16+
import java.util.concurrent.ConcurrentHashMap;
17+
18+
/**
19+
* ILM Provider implementation for the {@link ImmutableClusterStateHandlerProvider} service interface
20+
*/
21+
public class ILMImmutableStateHandlerProvider implements ImmutableClusterStateHandlerProvider {
22+
private static final Set<ImmutableClusterStateHandler<?>> handlers = ConcurrentHashMap.newKeySet();
23+
24+
@Override
25+
public Collection<ImmutableClusterStateHandler<?>> handlers() {
26+
return handlers;
27+
}
28+
29+
public static void registerHandlers(ImmutableClusterStateHandler<?>... stateHandlers) {
30+
handlers.addAll(Arrays.asList(stateHandlers));
31+
}
32+
}

x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
8484
import org.elasticsearch.xpack.core.slm.action.StartSLMAction;
8585
import org.elasticsearch.xpack.core.slm.action.StopSLMAction;
86+
import org.elasticsearch.xpack.ilm.action.ImmutableLifecycleAction;
8687
import org.elasticsearch.xpack.ilm.action.RestDeleteLifecycleAction;
8788
import org.elasticsearch.xpack.ilm.action.RestExplainLifecycleAction;
8889
import org.elasticsearch.xpack.ilm.action.RestGetLifecycleAction;
@@ -267,6 +268,10 @@ public Collection<Object> createComponents(
267268
components.addAll(Arrays.asList(snapshotLifecycleService.get(), snapshotHistoryStore.get(), snapshotRetentionService.get()));
268269
ilmHealthIndicatorService.set(new IlmHealthIndicatorService(clusterService));
269270
slmHealthIndicatorService.set(new SlmHealthIndicatorService(clusterService));
271+
272+
ILMImmutableStateHandlerProvider.registerHandlers(
273+
new ImmutableLifecycleAction(xContentRegistry, client, XPackPlugin.getSharedLicenseState())
274+
);
270275
return components;
271276
}
272277

0 commit comments

Comments
 (0)