Skip to content

Commit 29ebd80

Browse files
authored
Support wildcard/regex for indices param in _remotestore/_restore (opensearch-project#8922)
* Support wildcard/regex for indices param in _remotestore/_restore Signed-off-by: bansvaru <bansvaru@amazon.com>
1 parent ed33488 commit 29ebd80

File tree

8 files changed

+152
-124
lines changed

8 files changed

+152
-124
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2626
- [Tiered Caching] Expose new cache stats API ([#13237](https://github.com/opensearch-project/OpenSearch/pull/13237))
2727
- [Streaming Indexing] Ensure support of the new transport by security plugin ([#13174](https://github.com/opensearch-project/OpenSearch/pull/13174))
2828
- Add cluster setting to dynamically configure the buckets for filter rewrite optimization. ([#13179](https://github.com/opensearch-project/OpenSearch/pull/13179))
29+
- Support wildcard/regex for indices param in _remotestore/_restore ([#8922](https://github.com/opensearch-project/OpenSearch/pull/8922))
2930
- [Tiered Caching] Gate new stats logic behind FeatureFlags.PLUGGABLE_CACHE ([#13238](https://github.com/opensearch-project/OpenSearch/pull/13238))
3031
- [Tiered Caching] Add a dynamic setting to disable/enable disk cache. ([#13373](https://github.com/opensearch-project/OpenSearch/pull/13373))
3132
- [Remote Store] Add capability of doing refresh as determined by the translog ([#12992](https://github.com/opensearch-project/OpenSearch/pull/12992))

server/src/internalClusterTest/java/org/opensearch/remotestore/RemoteStoreRestoreIT.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount;
4343
import static org.hamcrest.Matchers.greaterThan;
4444

45-
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.SUITE, numDataNodes = 0)
45+
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0)
4646
public class RemoteStoreRestoreIT extends BaseRemoteStoreRestoreIT {
4747

4848
/**
@@ -295,7 +295,6 @@ public void testRestoreFlowNoRedIndex() throws Exception {
295295
* for multiple indices matching a wildcard name pattern.
296296
* @throws IOException IO Exception.
297297
*/
298-
@AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/8480")
299298
public void testRTSRestoreWithCommittedDataMultipleIndicesPatterns() throws Exception {
300299
testRestoreFlowMultipleIndices(2, true, randomIntBetween(1, 5));
301300
}
@@ -306,16 +305,16 @@ public void testRTSRestoreWithCommittedDataMultipleIndicesPatterns() throws Exce
306305
* with all remote-enabled red indices considered for the restore by default.
307306
* @throws IOException IO Exception.
308307
*/
309-
@AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/8480")
310308
public void testRTSRestoreWithCommittedDataDefaultAllIndices() throws Exception {
311309
int shardCount = randomIntBetween(1, 5);
312-
prepareCluster(1, 3, INDEX_NAMES, 1, shardCount);
310+
int replicaCount = 1;
311+
prepareCluster(1, 3, INDEX_NAMES, replicaCount, shardCount);
313312
String[] indices = INDEX_NAMES.split(",");
314313
Map<String, Map<String, Long>> indicesStats = new HashMap<>();
315314
for (String index : indices) {
316315
Map<String, Long> indexStats = indexData(2, true, index);
317316
indicesStats.put(index, indexStats);
318-
assertEquals(shardCount, getNumShards(index).totalNumShards);
317+
assertEquals(shardCount * (replicaCount + 1), getNumShards(index).totalNumShards);
319318
}
320319

321320
for (String index : indices) {
@@ -337,7 +336,7 @@ public void testRTSRestoreWithCommittedDataDefaultAllIndices() throws Exception
337336
ensureGreen(indices);
338337

339338
for (String index : indices) {
340-
assertEquals(shardCount, getNumShards(index).totalNumShards);
339+
assertEquals(shardCount * (replicaCount + 1), getNumShards(index).totalNumShards);
341340
verifyRestoredData(indicesStats.get(index), index);
342341
}
343342
}
@@ -395,16 +394,16 @@ public void testRTSRestoreWithCommittedDataNotAllRedRemoteIndices() throws Excep
395394
* except those matching the specified exclusion pattern.
396395
* @throws IOException IO Exception.
397396
*/
398-
@AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/8480")
399397
public void testRTSRestoreWithCommittedDataExcludeIndicesPatterns() throws Exception {
400398
int shardCount = randomIntBetween(1, 5);
401-
prepareCluster(1, 3, INDEX_NAMES, 1, shardCount);
399+
int replicaCount = 1;
400+
prepareCluster(1, 3, INDEX_NAMES, replicaCount, shardCount);
402401
String[] indices = INDEX_NAMES.split(",");
403402
Map<String, Map<String, Long>> indicesStats = new HashMap<>();
404403
for (String index : indices) {
405404
Map<String, Long> indexStats = indexData(2, true, index);
406405
indicesStats.put(index, indexStats);
407-
assertEquals(shardCount, getNumShards(index).totalNumShards);
406+
assertEquals(shardCount * (replicaCount + 1), getNumShards(index).totalNumShards);
408407
}
409408

410409
for (String index : indices) {
@@ -433,9 +432,9 @@ public void testRTSRestoreWithCommittedDataExcludeIndicesPatterns() throws Excep
433432
PlainActionFuture.newFuture()
434433
);
435434
ensureGreen(indices[0], indices[1]);
436-
assertEquals(shardCount, getNumShards(indices[0]).totalNumShards);
435+
assertEquals(shardCount * (replicaCount + 1), getNumShards(indices[0]).totalNumShards);
437436
verifyRestoredData(indicesStats.get(indices[0]), indices[0]);
438-
assertEquals(shardCount, getNumShards(indices[1]).totalNumShards);
437+
assertEquals(shardCount * (replicaCount + 1), getNumShards(indices[1]).totalNumShards);
439438
verifyRestoredData(indicesStats.get(indices[1]), indices[1]);
440439
ensureRed(indices[2], indices[3]);
441440
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.common.util;
10+
11+
import org.opensearch.action.support.IndicesOptions;
12+
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
13+
import org.opensearch.common.regex.Regex;
14+
import org.opensearch.index.IndexNotFoundException;
15+
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.Collections;
19+
import java.util.HashSet;
20+
import java.util.List;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.Stream;
24+
25+
/**
26+
* Common Utility methods for Indices.
27+
*
28+
* @opensearch.internal
29+
*/
30+
public class IndexUtils {
31+
32+
/**
33+
* Filters out list of available indices based on the list of selected indices.
34+
*
35+
* @param availableIndices list of available indices
36+
* @param selectedIndices list of selected indices
37+
* @param indicesOptions ignore indices flag
38+
* @return filtered out indices
39+
*/
40+
public static List<String> filterIndices(List<String> availableIndices, String[] selectedIndices, IndicesOptions indicesOptions) {
41+
if (IndexNameExpressionResolver.isAllIndices(Arrays.asList(selectedIndices))) {
42+
return availableIndices;
43+
}
44+
45+
// Move the exclusions to end of list to ensure they are processed
46+
// after explicitly selected indices are chosen.
47+
final List<String> excludesAtEndSelectedIndices = Stream.concat(
48+
Arrays.stream(selectedIndices).filter(s -> s.isEmpty() || s.charAt(0) != '-'),
49+
Arrays.stream(selectedIndices).filter(s -> !s.isEmpty() && s.charAt(0) == '-')
50+
).collect(Collectors.toUnmodifiableList());
51+
52+
Set<String> result = null;
53+
for (int i = 0; i < excludesAtEndSelectedIndices.size(); i++) {
54+
String indexOrPattern = excludesAtEndSelectedIndices.get(i);
55+
boolean add = true;
56+
if (!indexOrPattern.isEmpty()) {
57+
if (availableIndices.contains(indexOrPattern)) {
58+
if (result == null) {
59+
result = new HashSet<>();
60+
}
61+
result.add(indexOrPattern);
62+
continue;
63+
}
64+
if (indexOrPattern.charAt(0) == '+') {
65+
add = true;
66+
indexOrPattern = indexOrPattern.substring(1);
67+
// if its the first, add empty set
68+
if (i == 0) {
69+
result = new HashSet<>();
70+
}
71+
} else if (indexOrPattern.charAt(0) == '-') {
72+
// If the first index pattern is an exclusion, then all patterns are exclusions due to the
73+
// reordering logic above. In this case, the request is interpreted as "include all indexes except
74+
// those matching the exclusions" so we add all indices here and then remove the ones that match the exclusion patterns.
75+
if (i == 0) {
76+
result = new HashSet<>(availableIndices);
77+
}
78+
add = false;
79+
indexOrPattern = indexOrPattern.substring(1);
80+
}
81+
}
82+
if (indexOrPattern.isEmpty() || !Regex.isSimpleMatchPattern(indexOrPattern)) {
83+
if (!availableIndices.contains(indexOrPattern)) {
84+
if (!indicesOptions.ignoreUnavailable()) {
85+
throw new IndexNotFoundException(indexOrPattern);
86+
} else {
87+
if (result == null) {
88+
// add all the previous ones...
89+
result = new HashSet<>(availableIndices.subList(0, i));
90+
}
91+
}
92+
} else {
93+
if (result != null) {
94+
if (add) {
95+
result.add(indexOrPattern);
96+
} else {
97+
result.remove(indexOrPattern);
98+
}
99+
}
100+
}
101+
continue;
102+
}
103+
if (result == null) {
104+
// add all the previous ones...
105+
result = new HashSet<>(availableIndices.subList(0, i));
106+
}
107+
boolean found = false;
108+
for (String index : availableIndices) {
109+
if (Regex.simpleMatch(indexOrPattern, index)) {
110+
found = true;
111+
if (add) {
112+
result.add(index);
113+
} else {
114+
result.remove(index);
115+
}
116+
}
117+
}
118+
if (!found && !indicesOptions.allowNoIndices()) {
119+
throw new IndexNotFoundException(indexOrPattern);
120+
}
121+
}
122+
if (result == null) {
123+
return Collections.unmodifiableList(new ArrayList<>(Arrays.asList(selectedIndices)));
124+
}
125+
return Collections.unmodifiableList(new ArrayList<>(result));
126+
}
127+
128+
}

server/src/main/java/org/opensearch/index/recovery/RemoteStoreRestoreService.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.logging.log4j.LogManager;
1212
import org.apache.logging.log4j.Logger;
1313
import org.opensearch.action.admin.cluster.remotestore.restore.RestoreRemoteStoreRequest;
14+
import org.opensearch.action.support.IndicesOptions;
1415
import org.opensearch.cluster.ClusterState;
1516
import org.opensearch.cluster.ClusterStateUpdateTask;
1617
import org.opensearch.cluster.block.ClusterBlocks;
@@ -48,6 +49,7 @@
4849
import java.util.stream.Collectors;
4950

5051
import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_REMOTE_STORE_ENABLED;
52+
import static org.opensearch.common.util.IndexUtils.filterIndices;
5153
import static org.opensearch.repositories.blobstore.BlobStoreRepository.SYSTEM_REPOSITORY_SETTING;
5254

5355
/**
@@ -158,7 +160,12 @@ public RemoteRestoreResult restore(
158160
throw new IllegalStateException("Unable to restore remote index metadata", e);
159161
}
160162
} else {
161-
for (String indexName : indexNames) {
163+
List<String> filteredIndices = filterIndices(
164+
List.of(currentState.metadata().getConcreteAllIndices()),
165+
indexNames,
166+
IndicesOptions.fromOptions(true, true, true, true)
167+
);
168+
for (String indexName : filteredIndices) {
162169
IndexMetadata indexMetadata = currentState.metadata().index(indexName);
163170
if (indexMetadata == null) {
164171
logger.warn("Index restore is not supported for non-existent index. Skipping: {}", indexName);

server/src/main/java/org/opensearch/snapshots/RestoreService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@
125125
import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_VERSION_CREATED;
126126
import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_VERSION_UPGRADED;
127127
import static org.opensearch.common.util.FeatureFlags.SEARCHABLE_SNAPSHOT_EXTENDED_COMPATIBILITY;
128+
import static org.opensearch.common.util.IndexUtils.filterIndices;
128129
import static org.opensearch.common.util.set.Sets.newHashSet;
129130
import static org.opensearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
130131
import static org.opensearch.index.store.remote.directory.RemoteSnapshotDirectory.SEARCHABLE_SNAPSHOT_EXTENDED_COMPATIBILITY_MINIMUM_VERSION;
131132
import static org.opensearch.index.store.remote.filecache.FileCache.DATA_TO_FILE_CACHE_SIZE_RATIO_SETTING;
132133
import static org.opensearch.node.Node.NODE_SEARCH_CACHE_SIZE_SETTING;
133-
import static org.opensearch.snapshots.SnapshotUtils.filterIndices;
134134

135135
/**
136136
* Service responsible for restoring snapshots

server/src/main/java/org/opensearch/snapshots/SnapshotUtils.java

-105
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,15 @@
3131

3232
package org.opensearch.snapshots;
3333

34-
import org.opensearch.action.support.IndicesOptions;
3534
import org.opensearch.cluster.metadata.IndexMetadata;
36-
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
37-
import org.opensearch.common.regex.Regex;
3835
import org.opensearch.index.IndexModule;
39-
import org.opensearch.index.IndexNotFoundException;
4036
import org.opensearch.index.IndexSettings;
4137

42-
import java.util.ArrayList;
43-
import java.util.Arrays;
44-
import java.util.Collections;
4538
import java.util.HashMap;
4639
import java.util.HashSet;
4740
import java.util.List;
4841
import java.util.Map;
4942
import java.util.Set;
50-
import java.util.stream.Collectors;
51-
import java.util.stream.Stream;
5243

5344
/**
5445
* Snapshot utilities
@@ -57,102 +48,6 @@
5748
*/
5849
public class SnapshotUtils {
5950

60-
/**
61-
* Filters out list of available indices based on the list of selected indices.
62-
*
63-
* @param availableIndices list of available indices
64-
* @param selectedIndices list of selected indices
65-
* @param indicesOptions ignore indices flag
66-
* @return filtered out indices
67-
*/
68-
public static List<String> filterIndices(List<String> availableIndices, String[] selectedIndices, IndicesOptions indicesOptions) {
69-
if (IndexNameExpressionResolver.isAllIndices(Arrays.asList(selectedIndices))) {
70-
return availableIndices;
71-
}
72-
73-
// Move the exclusions to end of list to ensure they are processed
74-
// after explicitly selected indices are chosen.
75-
final List<String> excludesAtEndSelectedIndices = Stream.concat(
76-
Arrays.stream(selectedIndices).filter(s -> s.isEmpty() || s.charAt(0) != '-'),
77-
Arrays.stream(selectedIndices).filter(s -> !s.isEmpty() && s.charAt(0) == '-')
78-
).collect(Collectors.toUnmodifiableList());
79-
80-
Set<String> result = null;
81-
for (int i = 0; i < excludesAtEndSelectedIndices.size(); i++) {
82-
String indexOrPattern = excludesAtEndSelectedIndices.get(i);
83-
boolean add = true;
84-
if (!indexOrPattern.isEmpty()) {
85-
if (availableIndices.contains(indexOrPattern)) {
86-
if (result == null) {
87-
result = new HashSet<>();
88-
}
89-
result.add(indexOrPattern);
90-
continue;
91-
}
92-
if (indexOrPattern.charAt(0) == '+') {
93-
add = true;
94-
indexOrPattern = indexOrPattern.substring(1);
95-
// if its the first, add empty set
96-
if (i == 0) {
97-
result = new HashSet<>();
98-
}
99-
} else if (indexOrPattern.charAt(0) == '-') {
100-
// If the first index pattern is an exclusion, then all patterns are exclusions due to the
101-
// reordering logic above. In this case, the request is interpreted as "include all indexes except
102-
// those matching the exclusions" so we add all indices here and then remove the ones that match the exclusion patterns.
103-
if (i == 0) {
104-
result = new HashSet<>(availableIndices);
105-
}
106-
add = false;
107-
indexOrPattern = indexOrPattern.substring(1);
108-
}
109-
}
110-
if (indexOrPattern.isEmpty() || !Regex.isSimpleMatchPattern(indexOrPattern)) {
111-
if (!availableIndices.contains(indexOrPattern)) {
112-
if (!indicesOptions.ignoreUnavailable()) {
113-
throw new IndexNotFoundException(indexOrPattern);
114-
} else {
115-
if (result == null) {
116-
// add all the previous ones...
117-
result = new HashSet<>(availableIndices.subList(0, i));
118-
}
119-
}
120-
} else {
121-
if (result != null) {
122-
if (add) {
123-
result.add(indexOrPattern);
124-
} else {
125-
result.remove(indexOrPattern);
126-
}
127-
}
128-
}
129-
continue;
130-
}
131-
if (result == null) {
132-
// add all the previous ones...
133-
result = new HashSet<>(availableIndices.subList(0, i));
134-
}
135-
boolean found = false;
136-
for (String index : availableIndices) {
137-
if (Regex.simpleMatch(indexOrPattern, index)) {
138-
found = true;
139-
if (add) {
140-
result.add(index);
141-
} else {
142-
result.remove(index);
143-
}
144-
}
145-
}
146-
if (!found && !indicesOptions.allowNoIndices()) {
147-
throw new IndexNotFoundException(indexOrPattern);
148-
}
149-
}
150-
if (result == null) {
151-
return Collections.unmodifiableList(new ArrayList<>(Arrays.asList(selectedIndices)));
152-
}
153-
return Collections.unmodifiableList(new ArrayList<>(result));
154-
}
155-
15651
/**
15752
* Validates if there are any remote snapshots backing an index
15853
* @param metadata index metadata from cluster state

0 commit comments

Comments
 (0)