Skip to content

Commit 77ef1d4

Browse files
authored
Failure store - No selectors in snapshots (#120114)
In this PR we explore how we can include the failure store indices while disallowing the user to use selectors in the `indices` field of the create and restore snapshot requests. **Security concerns** The create and restore snapshot require cluster privileges only, so we do not have to worry about how to handle index level security (see [Create Snapshot API](https://www.elastic.co/guide/en/elasticsearch/reference/current/create-snapshot-api.html#create-snapshot-api-prereqs) & [Restore Snapshot API](https://www.elastic.co/guide/en/elasticsearch/reference/current/restore-snapshot-api.html#restore-snapshot-api-prereqs)). **Challenges** While there are other APIs that do not support selectors but they do include the failure indices in their response such as `GET _data_stream/<target>`, `GET _data_stream/<target>/stats`, etc; the snapshot APIs are a little bit different. The reason for that is that the data stream APIs first collect only the relevant data streams and then they select the backing indices and/or the failure indices. On the other hand, the snapshot API that works with both indices and data streams cannot take such a shortcut and needs to use the `concreteIndices` method. We propose, to add a flag that when the selectors are not "allowed" can determine if we need to include the failure store or not. In the past we had something similar called the default selector, but it was more flexible and it was used a fallback always. Our goal now is to only specify the behaviour we want when the selectors are not supported. This new flag allowed also to simplify the concrete index resolution in `GET _data_stream/<target>/stats` Relates to #119545
1 parent bb0d0ed commit 77ef1d4

File tree

15 files changed

+177
-145
lines changed

15 files changed

+177
-145
lines changed

modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamsSnapshotsIT.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.elasticsearch.client.internal.Client;
3838
import org.elasticsearch.cluster.metadata.DataStream;
3939
import org.elasticsearch.cluster.metadata.DataStreamAlias;
40-
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
4140
import org.elasticsearch.common.settings.Settings;
4241
import org.elasticsearch.common.unit.ByteSizeUnit;
4342
import org.elasticsearch.index.Index;
@@ -341,13 +340,13 @@ public void testSnapshotAndRestoreInPlace() {
341340
assertThat(rolloverResponse.getNewIndex(), equalTo(DataStream.getDefaultBackingIndexName("ds", 3)));
342341
}
343342

344-
public void testFailureStoreSnapshotAndRestore() throws Exception {
343+
public void testFailureStoreSnapshotAndRestore() {
345344
String dataStreamName = "with-fs";
346345
CreateSnapshotResponse createSnapshotResponse = client.admin()
347346
.cluster()
348347
.prepareCreateSnapshot(TEST_REQUEST_TIMEOUT, REPO, SNAPSHOT)
349348
.setWaitForCompletion(true)
350-
.setIndices(IndexNameExpressionResolver.combineSelector(dataStreamName, IndexComponentSelector.ALL_APPLICABLE))
349+
.setIndices(dataStreamName)
351350
.setIncludeGlobalState(false)
352351
.get();
353352

@@ -398,6 +397,49 @@ public void testFailureStoreSnapshotAndRestore() throws Exception {
398397
}
399398
}
400399

400+
public void testSelectorsNotAllowedInSnapshotAndRestore() {
401+
String dataStreamName = "with-fs";
402+
try {
403+
client.admin()
404+
.cluster()
405+
.prepareCreateSnapshot(TEST_REQUEST_TIMEOUT, REPO, SNAPSHOT)
406+
.setWaitForCompletion(true)
407+
.setIndices(dataStreamName + "::" + randomFrom(IndexComponentSelector.values()).getKey())
408+
.setIncludeGlobalState(false)
409+
.get();
410+
fail("Should have failed because selectors are not allowed in snapshot creation");
411+
} catch (IllegalArgumentException e) {
412+
assertThat(
413+
e.getMessage(),
414+
containsString("Index component selectors are not supported in this context but found selector in expression")
415+
);
416+
}
417+
CreateSnapshotResponse createSnapshotResponse = client.admin()
418+
.cluster()
419+
.prepareCreateSnapshot(TEST_REQUEST_TIMEOUT, REPO, SNAPSHOT)
420+
.setWaitForCompletion(true)
421+
.setIndices("ds")
422+
.setIncludeGlobalState(false)
423+
.get();
424+
425+
RestStatus status = createSnapshotResponse.getSnapshotInfo().status();
426+
assertEquals(RestStatus.OK, status);
427+
try {
428+
client.admin()
429+
.cluster()
430+
.prepareRestoreSnapshot(TEST_REQUEST_TIMEOUT, REPO, SNAPSHOT)
431+
.setWaitForCompletion(true)
432+
.setIndices(dataStreamName + "::" + randomFrom(IndexComponentSelector.values()).getKey())
433+
.get();
434+
fail("Should have failed because selectors are not allowed in snapshot restore");
435+
} catch (IllegalArgumentException e) {
436+
assertThat(
437+
e.getMessage(),
438+
containsString("Index component selectors are not supported in this context but found selector in expression")
439+
);
440+
}
441+
}
442+
401443
public void testSnapshotAndRestoreAllIncludeSpecificDataStream() throws Exception {
402444
DocWriteResponse indexResponse = client.prepareIndex("other-ds")
403445
.setOpType(DocWriteRequest.OpType.CREATE)
@@ -1241,6 +1283,7 @@ public void testExcludeDSFromSnapshotWhenExcludingAnyOfItsIndices() {
12411283
SnapshotInfo retrievedSnapshot = getSnapshot(REPO, SNAPSHOT);
12421284
assertThat(retrievedSnapshot.dataStreams(), contains(dataStreamName));
12431285
assertThat(retrievedSnapshot.indices(), containsInAnyOrder(fsBackingIndexName));
1286+
assertThat(retrievedSnapshot.indices(), not(containsInAnyOrder(fsFailureIndexName)));
12441287

12451288
assertAcked(
12461289
safeGet(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(TEST_REQUEST_TIMEOUT, "*")))

modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/DataStreamsStatsTransportAction.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.elasticsearch.action.datastreams.DataStreamsStatsAction;
1717
import org.elasticsearch.action.support.ActionFilters;
1818
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
19-
import org.elasticsearch.action.support.IndexComponentSelector;
2019
import org.elasticsearch.action.support.broadcast.node.TransportBroadcastByNodeAction;
2120
import org.elasticsearch.cluster.ClusterState;
2221
import org.elasticsearch.cluster.block.ClusterBlockException;
@@ -101,17 +100,6 @@ protected ClusterBlockException checkRequestBlock(
101100
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ, concreteIndices);
102101
}
103102

104-
@Override
105-
protected String[] resolveConcreteIndexNames(ClusterState clusterState, DataStreamsStatsAction.Request request) {
106-
return DataStreamsActionUtil.resolveConcreteIndexNamesWithSelector(
107-
indexNameExpressionResolver,
108-
clusterState,
109-
request.indices(),
110-
IndexComponentSelector.ALL_APPLICABLE,
111-
request.indicesOptions()
112-
).toArray(String[]::new);
113-
}
114-
115103
@Override
116104
protected ShardsIterator shards(ClusterState clusterState, DataStreamsStatsAction.Request request, String[] concreteIndices) {
117105
return clusterState.getRoutingTable().allShards(concreteIndices);

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ static TransportVersion def(int id) {
162162
public static final TransportVersion ADD_INDEX_BLOCK_TWO_PHASE = def(8_828_00_0);
163163
public static final TransportVersion RESOLVE_CLUSTER_NO_INDEX_EXPRESSION = def(8_829_00_0);
164164
public static final TransportVersion ML_ROLLOVER_LEGACY_INDICES = def(8_830_00_0);
165+
public static final TransportVersion ADD_INCLUDE_FAILURE_INDICES_OPTION = def(8_831_00_0);
165166

166167
/*
167168
* STOP! READ THIS FIRST! No, really,

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import org.elasticsearch.action.IndicesRequest;
1717
import org.elasticsearch.action.support.IndicesOptions;
1818
import org.elasticsearch.action.support.master.MasterNodeRequest;
19-
import org.elasticsearch.cluster.metadata.DataStream;
2019
import org.elasticsearch.common.Strings;
2120
import org.elasticsearch.common.UUIDs;
2221
import org.elasticsearch.common.bytes.BytesReference;
@@ -69,9 +68,7 @@ public class CreateSnapshotRequest extends MasterNodeRequest<CreateSnapshotReque
6968

7069
private String[] indices = EMPTY_ARRAY;
7170

72-
private IndicesOptions indicesOptions = DataStream.isFailureStoreFeatureFlagEnabled()
73-
? IndicesOptions.strictExpandHiddenIncludeFailureStore()
74-
: IndicesOptions.strictExpandHidden();
71+
private IndicesOptions indicesOptions = IndicesOptions.strictExpandHiddenFailureNoSelectors();
7572

7673
private String[] featureStates = EMPTY_ARRAY;
7774

server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/restore/RestoreSnapshotRequest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.action.ActionRequestValidationException;
1515
import org.elasticsearch.action.support.IndicesOptions;
1616
import org.elasticsearch.action.support.master.MasterNodeRequest;
17+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
1718
import org.elasticsearch.common.Strings;
1819
import org.elasticsearch.common.io.stream.StreamInput;
1920
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -43,7 +44,7 @@ public class RestoreSnapshotRequest extends MasterNodeRequest<RestoreSnapshotReq
4344
private String snapshot;
4445
private String repository;
4546
private String[] indices = Strings.EMPTY_ARRAY;
46-
private IndicesOptions indicesOptions = IndicesOptions.strictExpandOpen();
47+
private IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenFailureNoSelectors();
4748
private String[] featureStates = Strings.EMPTY_ARRAY;
4849
private String renamePattern;
4950
private String renameReplacement;
@@ -138,6 +139,16 @@ public ActionRequestValidationException validate() {
138139
if (indicesOptions == null) {
139140
validationException = addValidationError("indicesOptions is missing", validationException);
140141
}
142+
// This action does not use the IndexNameExpressionResolver to resolve concrete indices, this is why we check here for selectors
143+
if (indicesOptions.allowSelectors() == false) {
144+
for (String index : indices) {
145+
try {
146+
IndexNameExpressionResolver.SelectorResolver.parseExpression(index, indicesOptions);
147+
} catch (IllegalArgumentException e) {
148+
validationException = addValidationError(e.getMessage(), validationException);
149+
}
150+
}
151+
}
141152
if (featureStates == null) {
142153
validationException = addValidationError("featureStates is missing", validationException);
143154
}

server/src/main/java/org/elasticsearch/action/datastreams/DataStreamsActionUtil.java

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
package org.elasticsearch.action.datastreams;
1111

12-
import org.elasticsearch.action.support.IndexComponentSelector;
1312
import org.elasticsearch.action.support.IndicesOptions;
1413
import org.elasticsearch.cluster.ClusterState;
1514
import org.elasticsearch.cluster.metadata.DataStream;
@@ -55,64 +54,24 @@ public static List<String> resolveConcreteIndexNames(
5554
String[] names,
5655
IndicesOptions indicesOptions
5756
) {
58-
List<ResolvedExpression> abstractionNames = indexNameExpressionResolver.dataStreams(
57+
List<ResolvedExpression> resolvedDataStreamExpressions = indexNameExpressionResolver.dataStreams(
5958
clusterState,
6059
updateIndicesOptions(indicesOptions),
6160
names
6261
);
6362
SortedMap<String, IndexAbstraction> indicesLookup = clusterState.getMetadata().getIndicesLookup();
6463

65-
List<String> results = new ArrayList<>(abstractionNames.size());
66-
for (ResolvedExpression abstractionName : abstractionNames) {
67-
IndexAbstraction indexAbstraction = indicesLookup.get(abstractionName.resource());
64+
List<String> results = new ArrayList<>(resolvedDataStreamExpressions.size());
65+
for (ResolvedExpression resolvedExpression : resolvedDataStreamExpressions) {
66+
IndexAbstraction indexAbstraction = indicesLookup.get(resolvedExpression.resource());
6867
assert indexAbstraction != null;
6968
if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
70-
selectDataStreamIndicesNames(
71-
(DataStream) indexAbstraction,
72-
IndexComponentSelector.FAILURES.equals(abstractionName.selector()),
73-
results
74-
);
75-
}
76-
}
77-
return results;
78-
}
79-
80-
/**
81-
* Resolves a list of expressions into data stream names and then collects the concrete indices
82-
* that are applicable for those data streams based on the selector provided in the arguments.
83-
* @param indexNameExpressionResolver resolver object
84-
* @param clusterState state to query
85-
* @param names data stream expressions
86-
* @param selector which component indices of the data stream should be returned
87-
* @param indicesOptions options for expression resolution
88-
* @return A stream of concrete index names that belong to the components specified
89-
* on the data streams returned from the expressions given
90-
*/
91-
public static List<String> resolveConcreteIndexNamesWithSelector(
92-
IndexNameExpressionResolver indexNameExpressionResolver,
93-
ClusterState clusterState,
94-
String[] names,
95-
IndexComponentSelector selector,
96-
IndicesOptions indicesOptions
97-
) {
98-
assert indicesOptions.allowSelectors() == false : "If selectors are enabled, use resolveConcreteIndexNames instead";
99-
List<String> abstractionNames = indexNameExpressionResolver.dataStreamNames(
100-
clusterState,
101-
updateIndicesOptions(indicesOptions),
102-
names
103-
);
104-
SortedMap<String, IndexAbstraction> indicesLookup = clusterState.getMetadata().getIndicesLookup();
105-
106-
List<String> results = new ArrayList<>(abstractionNames.size());
107-
for (String abstractionName : abstractionNames) {
108-
IndexAbstraction indexAbstraction = indicesLookup.get(abstractionName);
109-
assert indexAbstraction != null;
110-
if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
111-
if (selector.shouldIncludeData()) {
112-
selectDataStreamIndicesNames((DataStream) indexAbstraction, false, results);
69+
DataStream dataStream = (DataStream) indexAbstraction;
70+
if (IndexNameExpressionResolver.shouldIncludeRegularIndices(indicesOptions, resolvedExpression.selector())) {
71+
selectDataStreamIndicesNames(dataStream, false, results);
11372
}
114-
if (selector.shouldIncludeFailures()) {
115-
selectDataStreamIndicesNames((DataStream) indexAbstraction, true, results);
73+
if (IndexNameExpressionResolver.shouldIncludeFailureIndices(indicesOptions, resolvedExpression.selector())) {
74+
selectDataStreamIndicesNames(dataStream, true, results);
11675
}
11776
}
11877
}

server/src/main/java/org/elasticsearch/action/datastreams/DataStreamsStatsAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public Request() {
5757
.allowClosedIndices(true)
5858
.ignoreThrottled(false)
5959
.allowSelectors(false)
60+
.includeFailureIndices(true)
6061
.build()
6162
)
6263
.build()

0 commit comments

Comments
 (0)