Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/128610.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 128610
summary: "Handle the indices pattern `[\"*\", \"-*\"]` when grouping indices by cluster\
\ name"
area: CCS
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -1246,7 +1246,7 @@ static <T> boolean isExplicitAllPattern(Collection<T> aliasesOrIndices, Function
/**
* Identifies if this expression list is *,-* which effectively means a request that requests no indices.
*/
static boolean isNoneExpression(String[] expressions) {
public static boolean isNoneExpression(String[] expressions) {
return expressions.length == 2 && "*".equals(expressions[0]) && "-*".equals(expressions[1]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.RefCountingRunnable;
import org.elasticsearch.client.internal.RemoteClusterClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
import org.elasticsearch.common.Strings;
Expand Down Expand Up @@ -195,7 +196,23 @@ boolean isRemoteNodeConnected(final String remoteCluster, final DiscoveryNode no
*/
public Map<String, OriginalIndices> groupIndices(IndicesOptions indicesOptions, String[] indices, boolean returnLocalAll) {
final Map<String, OriginalIndices> originalIndicesMap = new HashMap<>();
final Map<String, List<String>> groupedIndices = groupClusterIndices(getRemoteClusterNames(), indices);
final Map<String, List<String>> groupedIndices;
/*
* returnLocalAll is used to control whether we'd like to fallback to the local cluster.
* While this is acceptable in a few cases, there are cases where we should not fallback to the local
* cluster. Consider _resolve/cluster where the specified patterns do not match any remote clusters.
* Falling back to the local cluster and returning its details in such cases is not ok. This is why
* TransportResolveClusterAction sets returnLocalAll to false wherever it uses groupIndices().
*
* If such a fallback isn't allowed and the given indices match a pattern whose semantics mean that
* it's ok to return an empty result (denoted via ["*", "-*"]), empty groupIndices.
*/
if (returnLocalAll == false && IndexNameExpressionResolver.isNoneExpression(indices)) {
groupedIndices = Map.of();
} else {
groupedIndices = groupClusterIndices(getRemoteClusterNames(), indices);
}

if (groupedIndices.isEmpty()) {
if (returnLocalAll) {
// search on _all in the local cluster if neither local indices nor remote indices were specified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,21 +281,15 @@ public void testResolveClusterUnderRCS1() throws Exception {
);
assertThat(exc.getMessage(), containsString(indexOptionTuple.v1()));
}
// TODO: The security pathways are not using the new
// RemoteClusterService.groupIndices(IndicesOptions indicesOptions, String[] indices, boolean returnLocalAll) method
// so this use case still behaves badly - fix in follow on PR
// {
// // TEST CASE 13: Resolution against wildcarded remote cluster expression that matches no remotes
// final Request remoteOnly1 = new Request("GET", "_resolve/cluster/no_such_remote*:*");
// Response response = performRequestWithRemoteSearchUser(remoteOnly1);
// assertOK(response);
// Map<String, Object> responseMap = responseAsMap(response);
// assertThat(responseMap.get(LOCAL_CLUSTER_NAME_REPRESENTATION), nullValue());
// Map<String, Object> remoteMap = (Map<String, Object>) responseMap.get("my_remote_cluster");
// assertThat((Boolean) remoteMap.get("connected"), equalTo(true));
// assertThat((Boolean) remoteMap.get("matching_indices"), equalTo(false));
// assertThat(remoteMap.get("version"), notNullValue());
// }
{
// TEST CASE 13: Resolution against wildcarded remote cluster expression that matches no remotes should result in an
// empty response and not fall back to the local cluster.
final Request remoteOnly1 = new Request("GET", "_resolve/cluster/no_such_remote*:*");
Response response = performRequestWithRemoteSearchUser(remoteOnly1);
assertOK(response);
Map<String, Object> responseMap = responseAsMap(response);
assertThat(responseMap.isEmpty(), is(true));
}
}

private Response performRequestWithRemoteSearchUser(final Request request) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,19 +376,15 @@ public void testResolveCluster() throws Exception {
);
assertThat(exc.getMessage(), containsString(indexOptionTuple.v1()));
}
// TODO: fix this in a follow-on PR
// {
// // TEST CASE 13: Resolution against wildcarded remote cluster expression that matches no remotes
// final Request remoteOnly1 = new Request("GET", "_resolve/cluster/no_such_remote*:*");
// Response response = performRequestWithRemoteSearchUser(remoteOnly1);
// assertOK(response);
// Map<String, Object> responseMap = responseAsMap(response);
// assertThat(responseMap.get(LOCAL_CLUSTER_NAME_REPRESENTATION), nullValue());
// Map<String, Object> remoteMap = (Map<String, Object>) responseMap.get("my_remote_cluster");
// assertThat((Boolean) remoteMap.get("connected"), equalTo(true));
// assertThat((Boolean) remoteMap.get("matching_indices"), equalTo(false));
// assertThat(remoteMap.get("version"), notNullValue());
// }
{
// TEST CASE 13: Resolution against wildcarded remote cluster expression that matches no remotes should result in an
// empty response and not fall back to the local cluster.
final Request remoteOnly1 = new Request("GET", "_resolve/cluster/no_such_remote*:*");
Response response = performRequestWithRemoteSearchUser(remoteOnly1);
assertOK(response);
Map<String, Object> responseMap = responseAsMap(response);
assertThat(responseMap.isEmpty(), is(true));
}
}

private Response performRequestWithRemoteSearchUser(final Request request) throws IOException {
Expand Down