Skip to content

Commit

Permalink
Implement ILM/settings operator handlers (#88097)
Browse files Browse the repository at this point in the history
Relates to #86224
  • Loading branch information
grcevski authored Jul 4, 2022
1 parent e21c597 commit fc93f77
Show file tree
Hide file tree
Showing 15 changed files with 753 additions and 4 deletions.
2 changes: 2 additions & 0 deletions server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,6 @@
with
org.elasticsearch.cluster.coordination.NodeToolCliProvider,
org.elasticsearch.index.shard.ShardToolCliProvider;

uses org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.immutablestate.action.ImmutableClusterSettingsAction;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import static org.elasticsearch.common.settings.AbstractScopedSettings.ARCHIVED_SETTINGS_PREFIX;
Expand Down Expand Up @@ -128,6 +130,17 @@ private static boolean checkClearedBlockAndArchivedSettings(
return true;
}

@Override
protected Optional<String> immutableStateHandlerName() {
return Optional.of(ImmutableClusterSettingsAction.NAME);
}

@Override
protected Set<String> modifiedKeys(ClusterUpdateSettingsRequest request) {
Settings allSettings = Settings.builder().put(request.persistentSettings()).put(request.transientSettings()).build();
return allSettings.keySet();
}

private static final String UPDATE_TASK_SOURCE = "cluster_update_settings";
private static final String REROUTE_TASK_SOURCE = "reroute_after_cluster_update_settings";

Expand Down Expand Up @@ -243,6 +256,13 @@ public ClusterUpdateSettingsTask(
this.request = request;
}

/**
* Used by the immutable state handler {@link ImmutableClusterSettingsAction}
*/
public ClusterUpdateSettingsTask(final ClusterSettings clusterSettings, ClusterUpdateSettingsRequest request) {
this(clusterSettings, Priority.IMMEDIATE, request, null);
}

@Override
public ClusterState execute(final ClusterState currentState) {
final ClusterState clusterState = updater.updateSettings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
import org.elasticsearch.transport.TransportException;
import org.elasticsearch.transport.TransportService;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import static org.elasticsearch.core.Strings.format;
Expand Down Expand Up @@ -142,6 +145,33 @@ private ClusterBlockException checkBlockIfStateRecovered(Request request, Cluste
}
}

/**
* Override this method if the master node action also has an {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler}
* 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 immutable in 'operator' mode (file based settings, modules, plugins).
*
* @return an Optional of the {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler} name
*/
protected Optional<String> immutableStateHandlerName() {
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 immutable state handler logic (see {@link org.elasticsearch.immutablestate.ImmutableClusterStateHandler})
* to verify if the keys don't conflict with an existing key set as immutable.
*
* @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();
}

@Override
protected void doExecute(Task task, final Request request, ActionListener<Response> listener) {
ClusterState state = clusterService.state();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.immutablestate.action;

import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.action.admin.cluster.settings.TransportClusterUpdateSettingsAction;
import org.elasticsearch.client.internal.Requests;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.immutablestate.ImmutableClusterStateHandler;
import org.elasticsearch.immutablestate.TransformState;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.elasticsearch.common.util.Maps.asMap;

/**
* This Action is the immutable state save version of RestClusterUpdateSettingsAction
* <p>
* It is used by the ImmutableClusterStateController to update the persistent cluster settings.
* Since transient cluster settings are deprecated, this action doesn't support updating transient cluster settings.
*/
public class ImmutableClusterSettingsAction implements ImmutableClusterStateHandler<ClusterUpdateSettingsRequest> {

public static final String NAME = "cluster_settings";

private final ClusterSettings clusterSettings;

public ImmutableClusterSettingsAction(ClusterSettings clusterSettings) {
this.clusterSettings = clusterSettings;
}

@Override
public String name() {
return NAME;
}

@SuppressWarnings("unchecked")
private ClusterUpdateSettingsRequest prepare(Object input, Set<String> previouslySet) {
final ClusterUpdateSettingsRequest clusterUpdateSettingsRequest = Requests.clusterUpdateSettingsRequest();

Map<String, ?> source = asMap(input);
Map<String, Object> persistentSettings = new HashMap<>();
Set<String> toDelete = new HashSet<>(previouslySet);

source.forEach((k, v) -> {
persistentSettings.put(k, v);
toDelete.remove(k);
});

toDelete.forEach(k -> persistentSettings.put(k, null));

clusterUpdateSettingsRequest.persistentSettings(persistentSettings);
return clusterUpdateSettingsRequest;
}

@Override
public TransformState transform(Object input, TransformState prevState) {
ClusterUpdateSettingsRequest request = prepare(input, prevState.keys());

// allow empty requests, this is how we clean up settings
if (request.persistentSettings().isEmpty() == false) {
validate(request);
}

ClusterState state = prevState.state();

TransportClusterUpdateSettingsAction.ClusterUpdateSettingsTask updateSettingsTask =
new TransportClusterUpdateSettingsAction.ClusterUpdateSettingsTask(clusterSettings, request);

state = updateSettingsTask.execute(state);
Set<String> currentKeys = request.persistentSettings()
.keySet()
.stream()
.filter(k -> request.persistentSettings().hasValue(k))
.collect(Collectors.toSet());

return new TransformState(state, currentKeys);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.immutablestate.action;

import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.immutablestate.TransformState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentParserConfiguration;
import org.elasticsearch.xcontent.XContentType;

import java.util.Collections;

import static org.hamcrest.Matchers.containsInAnyOrder;

public class ImmutableClusterSettingsActionTests extends ESTestCase {

private TransformState processJSON(ImmutableClusterSettingsAction action, TransformState prevState, String json) throws Exception {
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, json)) {
return action.transform(parser.map(), prevState);
}
}

public void testValidation() throws Exception {
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);

ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
TransformState prevState = new TransformState(state, Collections.emptySet());
ImmutableClusterSettingsAction action = new ImmutableClusterSettingsAction(clusterSettings);

String badPolicyJSON = """
{
"indices.recovery.min_bytes_per_sec": "50mb"
}""";

assertEquals(
"persistent setting [indices.recovery.min_bytes_per_sec], not recognized",
expectThrows(IllegalArgumentException.class, () -> processJSON(action, prevState, badPolicyJSON)).getMessage()
);
}

public void testSetUnsetSettings() throws Exception {
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);

ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
TransformState prevState = new TransformState(state, Collections.emptySet());
ImmutableClusterSettingsAction action = new ImmutableClusterSettingsAction(clusterSettings);

String emptyJSON = "";

TransformState updatedState = processJSON(action, prevState, emptyJSON);
assertEquals(0, updatedState.keys().size());
assertEquals(prevState.state(), updatedState.state());

String settingsJSON = """
{
"indices.recovery.max_bytes_per_sec": "50mb",
"cluster": {
"remote": {
"cluster_one": {
"seeds": [
"127.0.0.1:9300"
]
}
}
}
}""";

prevState = updatedState;
updatedState = processJSON(action, prevState, settingsJSON);
assertThat(updatedState.keys(), containsInAnyOrder("indices.recovery.max_bytes_per_sec", "cluster.remote.cluster_one.seeds"));
assertEquals("50mb", updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
assertEquals("[127.0.0.1:9300]", updatedState.state().metadata().persistentSettings().get("cluster.remote.cluster_one.seeds"));

String oneSettingJSON = """
{
"indices.recovery.max_bytes_per_sec": "25mb"
}""";

prevState = updatedState;
updatedState = processJSON(action, prevState, oneSettingJSON);
assertThat(updatedState.keys(), containsInAnyOrder("indices.recovery.max_bytes_per_sec"));
assertEquals("25mb", updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
assertNull(updatedState.state().metadata().persistentSettings().get("cluster.remote.cluster_one.seeds"));

prevState = updatedState;
updatedState = processJSON(action, prevState, emptyJSON);
assertEquals(0, updatedState.keys().size());
assertNull(updatedState.state().metadata().persistentSettings().get("indices.recovery.max_bytes_per_sec"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.core.Strings.format;

public class DeleteLifecycleAction extends ActionType<AcknowledgedResponse> {
public static final DeleteLifecycleAction INSTANCE = new DeleteLifecycleAction();
public static final String NAME = "cluster:admin/ilm/delete";
Expand Down Expand Up @@ -75,6 +77,11 @@ public boolean equals(Object obj) {
return Objects.equals(policyName, other.policyName);
}

@Override
public String toString() {
return format("delete lifecycle policy [%s]", policyName);
}

}

}
4 changes: 4 additions & 0 deletions x-pack/plugin/ilm/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.elasticsearch.xpack.ilm.ILMImmutableStateHandlerProvider;

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
Expand All @@ -16,4 +18,6 @@
exports org.elasticsearch.xpack.ilm to org.elasticsearch.server;
exports org.elasticsearch.xpack.slm.action to org.elasticsearch.server;
exports org.elasticsearch.xpack.slm to org.elasticsearch.server;

provides org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider with ILMImmutableStateHandlerProvider;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.ilm;

import org.elasticsearch.immutablestate.ImmutableClusterStateHandler;
import org.elasticsearch.immutablestate.ImmutableClusterStateHandlerProvider;

import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* ILM Provider implementation for the {@link ImmutableClusterStateHandlerProvider} service interface
*/
public class ILMImmutableStateHandlerProvider implements ImmutableClusterStateHandlerProvider {
private static final Set<ImmutableClusterStateHandler<?>> handlers = ConcurrentHashMap.newKeySet();

@Override
public Collection<ImmutableClusterStateHandler<?>> handlers() {
return handlers;
}

public static void registerHandlers(ImmutableClusterStateHandler<?>... stateHandlers) {
handlers.addAll(Arrays.asList(stateHandlers));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import org.elasticsearch.xpack.core.slm.action.PutSnapshotLifecycleAction;
import org.elasticsearch.xpack.core.slm.action.StartSLMAction;
import org.elasticsearch.xpack.core.slm.action.StopSLMAction;
import org.elasticsearch.xpack.ilm.action.ImmutableLifecycleAction;
import org.elasticsearch.xpack.ilm.action.RestDeleteLifecycleAction;
import org.elasticsearch.xpack.ilm.action.RestExplainLifecycleAction;
import org.elasticsearch.xpack.ilm.action.RestGetLifecycleAction;
Expand Down Expand Up @@ -267,6 +268,10 @@ public Collection<Object> createComponents(
components.addAll(Arrays.asList(snapshotLifecycleService.get(), snapshotHistoryStore.get(), snapshotRetentionService.get()));
ilmHealthIndicatorService.set(new IlmHealthIndicatorService(clusterService));
slmHealthIndicatorService.set(new SlmHealthIndicatorService(clusterService));

ILMImmutableStateHandlerProvider.registerHandlers(
new ImmutableLifecycleAction(xContentRegistry, client, XPackPlugin.getSharedLicenseState())
);
return components;
}

Expand Down
Loading

0 comments on commit fc93f77

Please sign in to comment.