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:
parent
b118697368
commit
1bb094a27b
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue