Do not allow modify aliases on followers (#43017)

Now that aliases are replicated by a follower from its leader, this
commit prevents directly modifying aliases on follower indices.
This commit is contained in:
Jason Tedor 2019-06-09 22:43:55 -04:00
parent 0ebcb21d2c
commit 63bad28005
No known key found for this signature in database
GPG Key ID: FA89F05560F16BC5
11 changed files with 273 additions and 12 deletions

View File

@ -85,6 +85,7 @@ import org.elasticsearch.action.admin.cluster.storedscripts.TransportPutStoredSc
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction;
import org.elasticsearch.action.admin.cluster.tasks.TransportPendingClusterTasksAction;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.TransportIndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistAction;
import org.elasticsearch.action.admin.indices.alias.exists.TransportAliasesExistAction;
@ -362,6 +363,7 @@ public class ActionModule extends AbstractModule {
private final DestructiveOperations destructiveOperations;
private final RestController restController;
private final RequestValidators<PutMappingRequest> mappingRequestValidators;
private final RequestValidators<IndicesAliasesRequest> indicesAliasesRequestRequestValidators;
public ActionModule(boolean transportClient, Settings settings, IndexNameExpressionResolver indexNameExpressionResolver,
IndexScopedSettings indexScopedSettings, ClusterSettings clusterSettings, SettingsFilter settingsFilter,
@ -395,6 +397,8 @@ public class ActionModule extends AbstractModule {
}
mappingRequestValidators = new RequestValidators<>(
actionPlugins.stream().flatMap(p -> p.mappingRequestValidators().stream()).collect(Collectors.toList()));
indicesAliasesRequestRequestValidators = new RequestValidators<>(
actionPlugins.stream().flatMap(p -> p.indicesAliasesRequestValidators().stream()).collect(Collectors.toList()));
if (transportClient) {
restController = null;
@ -692,6 +696,7 @@ public class ActionModule extends AbstractModule {
bind(ActionFilters.class).toInstance(actionFilters);
bind(DestructiveOperations.class).toInstance(destructiveOperations);
bind(new TypeLiteral<RequestValidators<PutMappingRequest>>() {}).toInstance(mappingRequestValidators);
bind(new TypeLiteral<RequestValidators<IndicesAliasesRequest>>() {}).toInstance(indicesAliasesRequestRequestValidators);
if (false == transportClient) {
// Supporting classes only used when not a transport client

View File

@ -63,6 +63,7 @@ import static org.elasticsearch.common.xcontent.ObjectParser.fromList;
public class IndicesAliasesRequest extends AcknowledgedRequest<IndicesAliasesRequest> implements ToXContentObject {
private List<AliasActions> allAliasActions = new ArrayList<>();
private String origin = "";
// indices options that require every specified index to exist, expand wildcards only to open
// indices, don't allow that no indices are resolved from wildcard expressions and resolve the
@ -535,6 +536,15 @@ public class IndicesAliasesRequest extends AcknowledgedRequest<IndicesAliasesReq
}
}
public String origin() {
return origin;
}
public IndicesAliasesRequest origin(final String origin) {
this.origin = Objects.requireNonNull(origin);
return this;
}
/**
* Add the action to this request and validate it.
*/
@ -565,12 +575,23 @@ public class IndicesAliasesRequest extends AcknowledgedRequest<IndicesAliasesReq
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
allAliasActions = in.readList(AliasActions::new);
if (in.getVersion().onOrAfter(Version.V_7_3_0)) {
origin = in.readOptionalString();
} else {
origin = null;
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(allAliasActions);
// noinspection StatementWithEmptyBody
if (out.getVersion().onOrAfter(Version.V_7_3_0)) {
out.writeOptionalString(origin);
} else {
// nothing to do here, here for symmetry with IndicesAliasesRequest#readFrom
}
}
public IndicesOptions indicesOptions() {

View File

@ -21,6 +21,7 @@ package org.elasticsearch.action.admin.indices.alias;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
@ -37,6 +38,7 @@ import org.elasticsearch.cluster.metadata.MetaDataIndexAliasesService;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.index.Index;
import org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
@ -45,6 +47,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static java.util.Collections.unmodifiableList;
@ -55,14 +59,21 @@ import static java.util.Collections.unmodifiableList;
public class TransportIndicesAliasesAction extends TransportMasterNodeAction<IndicesAliasesRequest, AcknowledgedResponse> {
private final MetaDataIndexAliasesService indexAliasesService;
private final RequestValidators<IndicesAliasesRequest> requestValidators;
@Inject
public TransportIndicesAliasesAction(TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, MetaDataIndexAliasesService indexAliasesService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
public TransportIndicesAliasesAction(
final TransportService transportService,
final ClusterService clusterService,
final ThreadPool threadPool,
final MetaDataIndexAliasesService indexAliasesService,
final ActionFilters actionFilters,
final IndexNameExpressionResolver indexNameExpressionResolver,
final RequestValidators<IndicesAliasesRequest> requestValidators) {
super(IndicesAliasesAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver,
IndicesAliasesRequest::new);
this.indexAliasesService = indexAliasesService;
this.requestValidators = Objects.requireNonNull(requestValidators);
}
@Override
@ -96,23 +107,28 @@ public class TransportIndicesAliasesAction extends TransportMasterNodeAction<Ind
// Resolve all the AliasActions into AliasAction instances and gather all the aliases
Set<String> aliases = new HashSet<>();
for (AliasActions action : actions) {
String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(state, request.indicesOptions(), action.indices());
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request.indicesOptions(), action.indices());
final Optional<Exception> maybeException = requestValidators.validateRequest(request, state, concreteIndices);
if (maybeException.isPresent()) {
listener.onFailure(maybeException.get());
return;
}
Collections.addAll(aliases, action.getOriginalAliases());
for (String index : concreteIndices) {
for (final Index index : concreteIndices) {
switch (action.actionType()) {
case ADD:
for (String alias : concreteAliases(action, state.metaData(), index)) {
finalActions.add(new AliasAction.Add(index, alias, action.filter(), action.indexRouting(),
for (String alias : concreteAliases(action, state.metaData(), index.getName())) {
finalActions.add(new AliasAction.Add(index.getName(), alias, action.filter(), action.indexRouting(),
action.searchRouting(), action.writeIndex()));
}
break;
case REMOVE:
for (String alias : concreteAliases(action, state.metaData(), index)) {
finalActions.add(new AliasAction.Remove(index, alias));
for (String alias : concreteAliases(action, state.metaData(), index.getName())) {
finalActions.add(new AliasAction.Remove(index.getName(), alias));
}
break;
case REMOVE_INDEX:
finalActions.add(new AliasAction.RemoveIndex(index));
finalActions.add(new AliasAction.RemoveIndex(index.getName()));
break;
default:
throw new IllegalArgumentException("Unsupported action [" + action.actionType() + "]");

View File

@ -20,7 +20,6 @@
package org.elasticsearch.action.admin.indices.mapping.put;
import com.carrotsearch.hppc.ObjectHashSet;
import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;

View File

@ -23,6 +23,7 @@ import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.action.support.TransportAction;
@ -190,4 +191,8 @@ public interface ActionPlugin {
return Collections.emptyList();
}
default Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,136 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.action.admin.indices.alias;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistResponse;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
public class ValidateIndiesAliasesRequestIT extends ESSingleNodeTestCase {
public static class IndicesAliasesPlugin extends Plugin implements ActionPlugin {
static final Setting<List<String>> ALLOWED_ORIGINS_SETTING = Setting.listSetting(
"index.aliases.allowed_origins",
Collections.emptyList(),
Function.identity(),
Setting.Property.IndexScope,
Setting.Property.Dynamic);
@Override
public List<Setting<?>> getSettings() {
return Collections.singletonList(ALLOWED_ORIGINS_SETTING);
}
@Override
public Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.singletonList((request, state, indices) -> {
for (final Index index : indices) {
final List<String> allowedOrigins = ALLOWED_ORIGINS_SETTING.get(state.metaData().index(index).getSettings());
if (allowedOrigins.contains(request.origin()) == false) {
final String message = String.format(
Locale.ROOT,
"origin [%s] not allowed for index [%s]",
request.origin(),
index.getName());
return Optional.of(new IllegalStateException(message));
}
}
return Optional.empty();
});
}
}
@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
return Collections.singletonList(IndicesAliasesPlugin.class);
}
public void testAllowed() {
final Settings settings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("allowed"))
.build();
createIndex("index", settings);
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin("allowed");
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index").alias("alias"));
assertAcked(client().admin().indices().aliases(request).actionGet());
final GetAliasesResponse response = client().admin().indices().getAliases(new GetAliasesRequest("alias")).actionGet();
assertThat(response.getAliases().keys().size(), equalTo(1));
assertThat(response.getAliases().keys().iterator().next().value, equalTo("index"));
final List<AliasMetaData> aliasMetaData = response.getAliases().get("index");
assertThat(aliasMetaData, hasSize(1));
assertThat(aliasMetaData.get(0).alias(), equalTo("alias"));
}
public void testNotAllowed() {
final Settings settings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("allowed"))
.build();
createIndex("index", settings);
final String origin = randomFrom("", "not-allowed");
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin(origin);
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index").alias("alias"));
final Exception e = expectThrows(IllegalStateException.class, () -> client().admin().indices().aliases(request).actionGet());
assertThat(e, hasToString(containsString("origin [" + origin + "] not allowed for index [index]")));
}
public void testSomeAllowed() {
final Settings fooIndexSettings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("foo_allowed"))
.build();
createIndex("foo", fooIndexSettings);
final Settings barIndexSettings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("bar_allowed"))
.build();
createIndex("bar", barIndexSettings);
final String origin = randomFrom("foo_allowed", "bar_allowed");
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin(origin);
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("foo").alias("alias"));
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("bar").alias("alias"));
final Exception e = expectThrows(IllegalStateException.class, () -> client().admin().indices().aliases(request).actionGet());
final String index = "foo_allowed".equals(origin) ? "bar" : "foo";
assertThat(e, hasToString(containsString("origin [" + origin + "] not allowed for index [" + index + "]")));
final AliasesExistResponse response = client().admin().indices().aliasesExist(new GetAliasesRequest("alias")).actionGet();
assertFalse(response.exists());
}
}

View File

@ -10,6 +10,7 @@ import org.apache.lucene.util.SetOnce;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
@ -355,4 +356,8 @@ public class Ccr extends Plugin implements ActionPlugin, PersistentTaskPlugin, E
return Collections.singletonList(CcrRequests.CCR_PUT_MAPPING_REQUEST_VALIDATOR);
}
@Override
public Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.singletonList(CcrRequests.CCR_INDICES_ALIASES_REQUEST_VALIDATOR);
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -105,4 +106,23 @@ public final class CcrRequests {
return Optional.empty();
};
public static final RequestValidators.RequestValidator<IndicesAliasesRequest> CCR_INDICES_ALIASES_REQUEST_VALIDATOR =
(request, state, indices) -> {
if (request.origin() == null) {
return Optional.empty(); // an indices aliases request on old versions does not have origin
}
final List<Index> followingIndices = Arrays.stream(indices)
.filter(index -> {
final IndexMetaData indexMetaData = state.metaData().index(index);
return indexMetaData != null && CcrSettings.CCR_FOLLOWING_INDEX_SETTING.get(indexMetaData.getSettings());
}).collect(Collectors.toList());
if (followingIndices.isEmpty() == false && "ccr".equals(request.origin()) == false) {
final String errorMessage = "can't modify aliases on indices "
+ "[" + followingIndices.stream().map(Index::getName).collect(Collectors.joining(", ")) + "]; "
+ "aliases of following indices are self-replicated from their leader indices";
return Optional.of(new ElasticsearchStatusException(errorMessage, RestStatus.FORBIDDEN));
}
return Optional.empty();
};
}

View File

@ -305,10 +305,11 @@ public class ShardFollowTasksExecutor extends PersistentTasksExecutor<ShardFollo
aliasActions.add(IndicesAliasesRequest.AliasActions.remove().index(followerIndex.getName()).alias(aliasName));
}
final IndicesAliasesRequest request = new IndicesAliasesRequest();
if (aliasActions.isEmpty()) {
handler.accept(leaderIndexMetaData.getAliasesVersion());
} else {
final IndicesAliasesRequest request = new IndicesAliasesRequest();
request.origin("ccr");
aliasActions.forEach(request::addAliasAction);
followerClient.admin().indices().aliases(
request,

View File

@ -17,6 +17,9 @@ import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest;
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
@ -43,6 +46,7 @@ import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.health.ClusterIndexHealth;
import org.elasticsearch.cluster.health.ClusterShardHealth;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
@ -103,11 +107,13 @@ import static java.util.Collections.singletonMap;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.xpack.ccr.CcrRetentionLeases.retentionLeaseId;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
@ -412,6 +418,43 @@ public class IndexFollowingIT extends CcrIntegTestCase {
assertAcked(followerClient().admin().indices().putMapping(putMappingRequest).actionGet());
}
public void testDoNotAllowAddAliasToFollower() throws Exception {
final String leaderIndexSettings =
getIndexSettings(between(1, 2), between(0, 1), singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
assertAcked(leaderClient().admin().indices().prepareCreate("leader").setSource(leaderIndexSettings, XContentType.JSON));
followerClient().execute(PutFollowAction.INSTANCE, putFollow("leader", "follower")).get();
final IndicesAliasesRequest request = new IndicesAliasesRequest()
.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("follower").alias("follower_alias"));
final ElasticsearchStatusException e =
expectThrows(ElasticsearchStatusException.class, () -> followerClient().admin().indices().aliases(request).actionGet());
assertThat(
e,
hasToString(containsString("can't modify aliases on indices [follower]; "
+ "aliases of following indices are self-replicated from their leader indices")));
assertThat(e.status(), equalTo(RestStatus.FORBIDDEN));
}
public void testAddAliasAfterUnfollow() throws Exception {
final String leaderIndexSettings =
getIndexSettings(between(1, 2), between(0, 1), singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
assertAcked(leaderClient().admin().indices().prepareCreate("leader").setSource(leaderIndexSettings, XContentType.JSON));
followerClient().execute(PutFollowAction.INSTANCE, putFollow("leader", "follower")).get();
pauseFollow("follower");
followerClient().admin().indices().close(new CloseIndexRequest("follower")).actionGet();
assertAcked(followerClient().execute(UnfollowAction.INSTANCE, new UnfollowAction.Request("follower")).actionGet());
followerClient().admin().indices().open(new OpenIndexRequest("follower")).actionGet();
final IndicesAliasesRequest request = new IndicesAliasesRequest()
.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("follower").alias("follower_alias"));
assertAcked(followerClient().admin().indices().aliases(request).actionGet());
final GetAliasesResponse response =
followerClient().admin().indices().getAliases(new GetAliasesRequest("follower_alias")).actionGet();
assertThat(response.getAliases().keys().size(), equalTo(1));
assertThat(response.getAliases().keys().iterator().next().value, equalTo("follower"));
final List<AliasMetaData> aliasMetaData = response.getAliases().get("follower");
assertThat(aliasMetaData, hasSize(1));
assertThat(aliasMetaData.get(0).alias(), equalTo("follower_alias"));
}
public void testFollowIndex_backlog() throws Exception {
int numberOfShards = between(1, 5);
String leaderIndexSettings = getIndexSettings(numberOfShards, between(0, 1),

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.core;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.bootstrap.BootstrapCheck;
@ -439,8 +440,17 @@ public class LocalStateCompositeXPackPlugin extends XPackPlugin implements Scrip
.collect(Collectors.toList());
}
@Override
public Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return filterPlugins(ActionPlugin.class)
.stream()
.flatMap(p -> p.indicesAliasesRequestValidators().stream())
.collect(Collectors.toList());
}
private <T> List<T> filterPlugins(Class<T> type) {
return plugins.stream().filter(x -> type.isAssignableFrom(x.getClass())).map(p -> ((T)p))
.collect(Collectors.toList());
}
}