[7.x] Validate alias operations don't target data streams (#58327) (#58337)

This adds validation to make sure alias operations (add, remove, remove index)
don't target data streams or the backing indices.

(cherry picked from commit 816448990e464a02f3960f12f6f6644a8cce36a4)
Signed-off-by: Andrei Dan <andrei.dan@elastic.co>
This commit is contained in:
Andrei Dan 2020-06-18 20:23:07 +01:00 committed by GitHub
parent 4836236446
commit 30e777856f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 3 deletions

View File

@ -21,6 +21,7 @@ package org.elasticsearch.indices;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.datastream.CreateDataStreamAction;
import org.elasticsearch.action.admin.indices.datastream.DeleteDataStreamAction;
import org.elasticsearch.action.admin.indices.datastream.GetDataStreamAction;
@ -434,6 +435,36 @@ public class DataStreamIT extends ESIntegTestCase {
assertTrue(maybeE.isPresent());
}
public void testAliasActionsFailOnDataStreams() throws Exception {
createIndexTemplate("id1", "metrics-foo*", "@timestamp1");
String dataStreamName = "metrics-foo";
CreateDataStreamAction.Request createDataStreamRequest = new CreateDataStreamAction.Request(dataStreamName);
client().admin().indices().createDataStream(createDataStreamRequest).get();
IndicesAliasesRequest.AliasActions addAction = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD)
.index(dataStreamName).aliases("foo");
IndicesAliasesRequest aliasesAddRequest = new IndicesAliasesRequest();
aliasesAddRequest.addAliasAction(addAction);
expectFailure(dataStreamName, () -> client().admin().indices().aliases(aliasesAddRequest).actionGet());
}
public void testAliasActionsFailOnDataStreamBackingIndices() throws Exception {
createIndexTemplate("id1", "metrics-foo*", "@timestamp1");
String dataStreamName = "metrics-foo";
CreateDataStreamAction.Request createDataStreamRequest = new CreateDataStreamAction.Request(dataStreamName);
client().admin().indices().createDataStream(createDataStreamRequest).get();
String backingIndex = DataStream.getDefaultBackingIndexName(dataStreamName, 1);
IndicesAliasesRequest.AliasActions addAction = new IndicesAliasesRequest.AliasActions(IndicesAliasesRequest.AliasActions.Type.ADD)
.index(backingIndex).aliases("first_gen");
IndicesAliasesRequest aliasesAddRequest = new IndicesAliasesRequest();
aliasesAddRequest.addAliasAction(addAction);
Exception e = expectThrows(IllegalArgumentException.class, () -> client().admin().indices().aliases(aliasesAddRequest).actionGet());
assertThat(e.getMessage(), equalTo("The provided expressions [" + backingIndex
+ "] match a backing index belonging to data stream [" + dataStreamName + "]. Data streams and their backing indices don't " +
"support aliases."));
}
private static void verifyResolvability(String dataStream, ActionRequestBuilder requestBuilder, boolean fail) {
verifyResolvability(dataStream, requestBuilder, fail, 0);
}

View File

@ -34,6 +34,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.AliasAction;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.IndexAbstraction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.MetadataIndexAliasesService;
@ -109,16 +110,26 @@ public class TransportIndicesAliasesAction extends TransportMasterNodeAction<Ind
//Expand the indices names
List<AliasActions> actions = request.aliasActions();
List<AliasAction> finalActions = new ArrayList<>();
// Resolve all the AliasActions into AliasAction instances and gather all the aliases
Set<String> aliases = new HashSet<>();
for (AliasActions action : actions) {
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request.indicesOptions(), action.indices());
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request.indicesOptions(), false,
action.indices());
for (Index concreteIndex : concreteIndices) {
IndexAbstraction indexAbstraction = state.metadata().getIndicesLookup().get(concreteIndex.getName());
assert indexAbstraction != null : "invalid cluster metadata. index [" + concreteIndex.getName() + "] was not found";
if (indexAbstraction.getParentDataStream() != null) {
throw new IllegalArgumentException("The provided expressions [" + String.join(",", action.indices())
+ "] match a backing index belonging to data stream [" + indexAbstraction.getParentDataStream().getName()
+ "]. Data streams and their backing indices don't support aliases.");
}
}
final Optional<Exception> maybeException = requestValidators.validateRequest(request, state, concreteIndices);
if (maybeException.isPresent()) {
listener.onFailure(maybeException.get());
return;
}
Collections.addAll(aliases, action.getOriginalAliases());
for (final Index index : concreteIndices) {
switch (action.actionType()) {

View File

@ -108,6 +108,7 @@ public class MetadataIndexAliasesService {
if (index == null) {
throw new IndexNotFoundException(action.getIndex());
}
validateAliasTargetIsNotDSBackingIndex(currentState, action);
indicesToDelete.add(index.getIndex());
changed = true;
}
@ -128,6 +129,7 @@ public class MetadataIndexAliasesService {
if (index == null) {
throw new IndexNotFoundException(action.getIndex());
}
validateAliasTargetIsNotDSBackingIndex(currentState, action);
NewAliasValidator newAliasValidator = (alias, indexRouting, filter, writeIndex) -> {
/* It is important that we look up the index using the metadata builder we are modifying so we can remove an
* index and replace it with an alias. */
@ -187,4 +189,13 @@ public class MetadataIndexAliasesService {
}
}
private void validateAliasTargetIsNotDSBackingIndex(ClusterState currentState, AliasAction action) {
IndexAbstraction indexAbstraction = currentState.metadata().getIndicesLookup().get(action.getIndex());
assert indexAbstraction != null : "invalid cluster metadata. index [" + action.getIndex() + "] was not found";
if (indexAbstraction.getParentDataStream() != null) {
throw new IllegalArgumentException("The provided index [ " + action.getIndex()
+ "] is a backing index belonging to data stream [" + indexAbstraction.getParentDataStream().getName()
+ "]. Data streams and their backing indices don't support alias operations.");
}
}
}

View File

@ -1688,6 +1688,40 @@ public class IndexNameExpressionResolverTests extends ESTestCase {
}
}
public void testIndicesAliasesRequestTargetDataStreams() {
final String dataStreamName = "my-data-stream";
IndexMetadata backingIndex = createBackingIndex(dataStreamName, 1).build();
Metadata.Builder mdBuilder = Metadata.builder()
.put(backingIndex, false)
.put(new DataStream(dataStreamName, "ts", org.elasticsearch.common.collect.List.of(backingIndex.getIndex()), 1));
ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build();
{
IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index(dataStreamName);
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
() -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions, false));
assertEquals("The provided expression [" + dataStreamName + "] matches a data stream, specify the corresponding " +
"concrete indices instead.", iae.getMessage());
}
{
IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index("my-data-*").alias("my-data");
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
() -> indexNameExpressionResolver.concreteIndexNames(state, aliasActions, false));
assertEquals("The provided expression [my-data-*] matches a data stream, specify the corresponding concrete indices instead.",
iae.getMessage());
}
{
IndicesAliasesRequest.AliasActions aliasActions = IndicesAliasesRequest.AliasActions.add().index(dataStreamName)
.alias("my-data");
String[] indices = indexNameExpressionResolver.concreteIndexNames(state, aliasActions, true);
assertEquals(1, indices.length);
assertEquals(backingIndex.getIndex().getName(), indices[0]);
}
}
public void testInvalidIndex() {
Metadata.Builder mdBuilder = Metadata.builder().put(indexBuilder("test"));
ClusterState state = ClusterState.builder(new ClusterName("_name")).metadata(mdBuilder).build();

View File

@ -41,6 +41,7 @@ import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anySetOf;
@ -488,6 +489,24 @@ public class MetadataIndexAliasesServiceTests extends ESTestCase {
}
}
public void testAliasesForDataStreamBackingIndicesNotSupported() {
String dataStreamName = "foo-stream";
String backingIndexName = DataStream.getDefaultBackingIndexName(dataStreamName, 1);
IndexMetadata indexMetadata = IndexMetadata.builder(backingIndexName)
.settings(settings(Version.CURRENT)).numberOfShards(1).numberOfReplicas(1).build();
ClusterState state = ClusterState.builder(ClusterName.DEFAULT)
.metadata(
Metadata.builder()
.put(indexMetadata, true)
.put(new DataStream(dataStreamName, "@timestamp", singletonList(indexMetadata.getIndex()))))
.build();
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> service.applyAliasActions(state,
singletonList(new AliasAction.Add(backingIndexName, "test", null, null, null, null, null))));
assertThat(exception.getMessage(), is("The provided index [ .ds-foo-stream-000001] is a backing index belonging to data stream " +
"[foo-stream]. Data streams and their backing indices don't support alias operations."));
}
private ClusterState applyHiddenAliasMix(ClusterState before, Boolean isHidden1, Boolean isHidden2) {
return service.applyAliasActions(before, Arrays.asList(
new AliasAction.Add("test", "alias", null, null, null, null, isHidden1),