Return 404 when deleting a non existing data stream (#62224)

Backport of #62059 to 7.x branch.

Return a 404 http status code when attempting to delete a non existing data stream.
However only return a 404 when targeting a data stream without any wildcards.

Closes #62022
This commit is contained in:
Martijn van Groningen 2020-09-11 15:36:05 +02:00 committed by GitHub
parent b118697368
commit 1bb094a27b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 18 deletions

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.action; package org.elasticsearch.xpack.core.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
@ -13,6 +14,7 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.master.MasterNodeRequest; import org.elasticsearch.action.support.master.MasterNodeRequest;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.common.util.CollectionUtils;
import java.io.IOException; import java.io.IOException;
@ -34,8 +36,17 @@ public class DeleteDataStreamAction extends ActionType<AcknowledgedResponse> {
private String[] names; private String[] names;
// Security intercepts requests and rewrites names if wildcards are used to expand to concrete resources
// that a user has privileges for.
// This keeps track whether wildcards were originally specified in names,
// So that in the case no matches ds are found, that either an
// empty response can be returned in case wildcards were used or
// 404 status code returned in case no wildcard were used.
private final boolean wildcardExpressionsOriginallySpecified;
public Request(String[] names) { public Request(String[] names) {
this.names = Objects.requireNonNull(names); this.names = Objects.requireNonNull(names);
this.wildcardExpressionsOriginallySpecified = Arrays.stream(names).anyMatch(Regex::isSimpleMatchPattern);
} }
public String[] getNames() { public String[] getNames() {
@ -54,12 +65,16 @@ public class DeleteDataStreamAction extends ActionType<AcknowledgedResponse> {
public Request(StreamInput in) throws IOException { public Request(StreamInput in) throws IOException {
super(in); super(in);
this.names = in.readStringArray(); this.names = in.readStringArray();
this.wildcardExpressionsOriginallySpecified = in.getVersion().onOrAfter(Version.V_7_10_0) && in.readBoolean();
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeStringArray(names); out.writeStringArray(names);
if (out.getVersion().onOrAfter(Version.V_7_10_0)) {
out.writeBoolean(wildcardExpressionsOriginallySpecified);
}
} }
@Override @Override
@ -67,12 +82,15 @@ public class DeleteDataStreamAction extends ActionType<AcknowledgedResponse> {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Request request = (Request) o; Request request = (Request) o;
return Arrays.equals(names, request.names); return wildcardExpressionsOriginallySpecified == request.wildcardExpressionsOriginallySpecified &&
Arrays.equals(names, request.names);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(names); int result = Objects.hash(wildcardExpressionsOriginallySpecified);
result = 31 * result + Arrays.hashCode(names);
return result;
} }
@Override @Override
@ -97,6 +115,10 @@ public class DeleteDataStreamAction extends ActionType<AcknowledgedResponse> {
this.names = indices; this.names = indices;
return this; return this;
} }
public boolean isWildcardExpressionsOriginallySpecified() {
return wildcardExpressionsOriginallySpecified;
}
} }
} }

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.datastreams.action;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.support.master.AcknowledgedResponse;
@ -25,7 +26,6 @@ import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.snapshots.SnapshotInProgressException; import org.elasticsearch.snapshots.SnapshotInProgressException;
@ -35,6 +35,7 @@ import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.action.DeleteDataStreamAction; import org.elasticsearch.xpack.core.action.DeleteDataStreamAction;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -97,7 +98,7 @@ public class DeleteDataStreamTransportAction extends TransportMasterNodeAction<D
@Override @Override
public ClusterState execute(ClusterState currentState) { public ClusterState execute(ClusterState currentState) {
return removeDataStream(deleteIndexService, currentState, request); return removeDataStream(deleteIndexService, indexNameExpressionResolver, currentState, request);
} }
@Override @Override
@ -110,19 +111,21 @@ public class DeleteDataStreamTransportAction extends TransportMasterNodeAction<D
static ClusterState removeDataStream( static ClusterState removeDataStream(
MetadataDeleteIndexService deleteIndexService, MetadataDeleteIndexService deleteIndexService,
IndexNameExpressionResolver indexNameExpressionResolver,
ClusterState currentState, ClusterState currentState,
DeleteDataStreamAction.Request request DeleteDataStreamAction.Request request
) { ) {
Set<String> dataStreams = new HashSet<>(); Set<String> dataStreams = new HashSet<>(
Set<String> snapshottingDataStreams = new HashSet<>(); indexNameExpressionResolver.dataStreamNames(currentState, request.indicesOptions(), request.getNames())
for (String name : request.getNames()) { );
for (String dataStreamName : currentState.metadata().dataStreams().keySet()) { Set<String> snapshottingDataStreams = SnapshotsService.snapshottingDataStreams(currentState, dataStreams);
if (Regex.simpleMatch(name, dataStreamName)) {
dataStreams.add(dataStreamName);
}
}
snapshottingDataStreams.addAll(SnapshotsService.snapshottingDataStreams(currentState, dataStreams)); if (dataStreams.isEmpty()) {
if (request.isWildcardExpressionsOriginallySpecified()) {
return currentState;
} else {
throw new ResourceNotFoundException("data streams " + Arrays.toString(request.getNames()) + " not found");
}
} }
if (snapshottingDataStreams.isEmpty() == false) { if (snapshottingDataStreams.isEmpty() == false) {

View File

@ -6,9 +6,11 @@
package org.elasticsearch.xpack.datastreams.action; package org.elasticsearch.xpack.datastreams.action;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.cluster.SnapshotsInProgress;
import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.cluster.metadata.DataStream;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.MetadataDeleteIndexService; import org.elasticsearch.cluster.metadata.MetadataDeleteIndexService;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -29,19 +31,22 @@ import java.util.Set;
import static org.elasticsearch.cluster.DataStreamTestHelper.getClusterStateWithDataStreams; import static org.elasticsearch.cluster.DataStreamTestHelper.getClusterStateWithDataStreams;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class DeleteDataStreamTransportActionTests extends ESTestCase { public class DeleteDataStreamTransportActionTests extends ESTestCase {
private final IndexNameExpressionResolver iner = new IndexNameExpressionResolver();
public void testDeleteDataStream() { public void testDeleteDataStream() {
final String dataStreamName = "my-data-stream"; final String dataStreamName = "my-data-stream";
final List<String> otherIndices = randomSubsetOf(Arrays.asList("foo", "bar", "baz")); final List<String> otherIndices = randomSubsetOf(Arrays.asList("foo", "bar", "baz"));
ClusterState cs = getClusterStateWithDataStreams(Collections.singletonList(new Tuple<>(dataStreamName, 2)), otherIndices); ClusterState cs = getClusterStateWithDataStreams(Collections.singletonList(new Tuple<>(dataStreamName, 2)), otherIndices);
DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName }); DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName });
ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), cs, req); ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), iner, cs, req);
assertThat(newState.metadata().dataStreams().size(), equalTo(0)); assertThat(newState.metadata().dataStreams().size(), equalTo(0));
assertThat(newState.metadata().indices().size(), equalTo(otherIndices.size())); assertThat(newState.metadata().indices().size(), equalTo(otherIndices.size()));
for (String indexName : otherIndices) { for (String indexName : otherIndices) {
@ -62,7 +67,7 @@ public class DeleteDataStreamTransportActionTests extends ESTestCase {
); );
DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { "ba*", "eggplant" }); DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { "ba*", "eggplant" });
ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), cs, req); ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), iner, cs, req);
assertThat(newState.metadata().dataStreams().size(), equalTo(1)); assertThat(newState.metadata().dataStreams().size(), equalTo(1));
DataStream remainingDataStream = newState.metadata().dataStreams().get(dataStreamNames[0]); DataStream remainingDataStream = newState.metadata().dataStreams().get(dataStreamNames[0]);
assertNotNull(remainingDataStream); assertNotNull(remainingDataStream);
@ -89,7 +94,7 @@ public class DeleteDataStreamTransportActionTests extends ESTestCase {
DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName }); DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName });
SnapshotInProgressException e = expectThrows( SnapshotInProgressException e = expectThrows(
SnapshotInProgressException.class, SnapshotInProgressException.class,
() -> DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), snapshotCs, req) () -> DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), iner, snapshotCs, req)
); );
assertThat( assertThat(
@ -130,8 +135,20 @@ public class DeleteDataStreamTransportActionTests extends ESTestCase {
), ),
Collections.emptyList() Collections.emptyList()
); );
DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName });
ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), cs, req); expectThrows(
ResourceNotFoundException.class,
() -> DeleteDataStreamTransportAction.removeDataStream(
getMetadataDeleteIndexService(),
iner,
cs,
new DeleteDataStreamAction.Request(new String[] { dataStreamName })
)
);
DeleteDataStreamAction.Request req = new DeleteDataStreamAction.Request(new String[] { dataStreamName + "*" });
ClusterState newState = DeleteDataStreamTransportAction.removeDataStream(getMetadataDeleteIndexService(), iner, cs, req);
assertThat(newState, sameInstance(cs));
assertThat(newState.metadata().dataStreams().size(), equalTo(cs.metadata().dataStreams().size())); assertThat(newState.metadata().dataStreams().size(), equalTo(cs.metadata().dataStreams().size()));
assertThat( assertThat(
newState.metadata().dataStreams().keySet(), newState.metadata().dataStreams().keySet(),

View File

@ -250,6 +250,51 @@ setup:
indices.get: indices.get:
index: ".ds-simple-data-stream1-000001" index: ".ds-simple-data-stream1-000001"
---
"Delete data stream missing behaviour":
- skip:
version: " - 7.8.99"
reason: "data streams only supported in 7.9+"
- do:
indices.create_data_stream:
name: simple-data-stream1
- is_true: acknowledged
- do:
indices.create_data_stream:
name: simple-data-stream2
- is_true: acknowledged
- do:
indices.create:
index: simple-data-streamz
- do:
indices.delete_data_stream:
name: simple-data-stream1
- is_true: acknowledged
- do:
indices.delete_data_stream:
name: simple-data-stream2
- is_true: acknowledged
- do:
indices.delete_data_stream:
name: simple-data-stream*
- is_true: acknowledged
- do:
catch: missing
indices.delete_data_stream:
name: simple-data-stream1
- do:
catch: missing
indices.delete_data_stream:
name: simple-data-stream2
--- ---
"append-only writes to backing indices prohobited": "append-only writes to backing indices prohobited":
- skip: - skip: