diff --git a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java index f398485768587..550ed3c78c49d 100644 --- a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java +++ b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java @@ -27,6 +27,7 @@ import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; +import org.hamcrest.Matcher; import org.junit.Before; import org.junit.ClassRule; @@ -299,32 +300,38 @@ public void testAliasFilter() throws Exception { } public void testUnauthorizedIndices() throws IOException { - ResponseException error; - error = expectThrows(ResponseException.class, () -> runESQLCommand("user1", "from index-user2 | stats sum(value)")); - assertThat(error.getMessage(), containsString("Unknown index [index-user2]")); - assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(400)); - - error = expectThrows(ResponseException.class, () -> runESQLCommand("user2", "from index-user1 | stats sum(value)")); - assertThat(error.getMessage(), containsString("Unknown index [index-user1]")); - assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(400)); - - error = expectThrows(ResponseException.class, () -> runESQLCommand("alias_user2", "from index-user2 | stats sum(value)")); - assertThat(error.getMessage(), containsString("Unknown index [index-user2]")); - assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(400)); - - error = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", "from index-user1 | stats sum(value)")); - assertThat(error.getMessage(), containsString("Unknown index [index-user1]")); - assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + expectThrowsResponseException( + () -> runESQLCommand("user1", "from index-user2 | stats sum(value)"), + HttpStatus.SC_FORBIDDEN, + containsString("unauthorized for user [test-admin] run as [user1] with effective roles [user1] on indices [index-user2]") + ); + expectThrowsResponseException( + () -> runESQLCommand("user2", "from index-user1 | stats sum(value)"), + HttpStatus.SC_FORBIDDEN, + containsString("unauthorized for user [test-admin] run as [user2] with effective roles [user2] on indices [index-user1]") + ); + expectThrowsResponseException( + () -> runESQLCommand("alias_user2", "from index-user2 | stats sum(value)"), + HttpStatus.SC_FORBIDDEN, + containsString( + "unauthorized for user [test-admin] run as [alias_user2] with effective roles [alias_user2] on indices [index-user2]" + ) + ); + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", "from index-user1 | stats sum(value)"), + HttpStatus.SC_FORBIDDEN, + containsString( + "unauthorized for user [test-admin] run as [metadata1_read2] with effective roles [metadata1_read2] on indices [index-user1]" + ) + ); } public void testInsufficientPrivilege() { - ResponseException error = expectThrows( - ResponseException.class, - () -> runESQLCommand("metadata1_read2", "FROM index-user1 | STATS sum=sum(value)") + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", "FROM index-user1 | STATS sum=sum(value)"), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "index-user1")) ); - logger.info("error", error); - assertThat(error.getMessage(), containsString("Unknown index [index-user1]")); - assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); } public void testIndexPatternErrorMessageComparison_ESQL_SearchDSL() throws Exception { @@ -917,7 +924,7 @@ public void testLookupJoinIndexForbidden() throws Exception { testLookupJoinIndexForbiddenHelper(true); } - private void testLookupJoinIndexForbiddenHelper(boolean useExpressionJoin) throws Exception { + private void testLookupJoinIndexForbiddenHelper(boolean useExpressionJoin) { assumeTrue( "Requires LOOKUP JOIN capability", hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) @@ -926,44 +933,56 @@ private void testLookupJoinIndexForbiddenHelper(boolean useExpressionJoin) throw String query1 = useExpressionJoin ? "FROM lookup-user2 | EVAL value_left = 10.0 | LOOKUP JOIN lookup-user1 ON value_left == value | KEEP x" : "FROM lookup-user2 | EVAL value = 10.0 | LOOKUP JOIN lookup-user1 ON value | KEEP x"; - var resp = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", query1)); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", query1), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "lookup-user1")) + ); String query2 = useExpressionJoin ? "FROM lookup-user2 | EVAL value_left = 10.0 | LOOKUP JOIN lookup-first-alias ON value_left == value | KEEP x" : "FROM lookup-user2 | EVAL value = 10.0 | LOOKUP JOIN lookup-first-alias ON value | KEEP x"; - resp = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", query2)); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-first-alias]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", query2), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "lookup-first-alias")) + ); String query3 = useExpressionJoin ? "ROW x = 10.0 | EVAL value_left = x | LOOKUP JOIN lookup-user1 ON value_left == value | KEEP x" : "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN lookup-user1 ON value | KEEP x"; - resp = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", query3)); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", query3), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "lookup-user1")) + ); String query4 = useExpressionJoin ? "ROW x = 10.0 | EVAL value_left = x | LOOKUP JOIN lookup-user1 ON value_left == value | KEEP x" : "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN lookup-user1 ON value | KEEP x"; - resp = expectThrows(ResponseException.class, () -> runESQLCommand("alias_user1", query4)); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + expectThrowsResponseException( + () -> runESQLCommand("alias_user1", query4), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("alias_user1", "lookup-user1")) + ); } - public void testFromLookupIndexForbidden() throws Exception { - var resp = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", "FROM lookup-user1")); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - - resp = expectThrows(ResponseException.class, () -> runESQLCommand("metadata1_read2", "FROM lookup-first-alias")); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-first-alias]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); - - resp = expectThrows(ResponseException.class, () -> runESQLCommand("alias_user1", "FROM lookup-user1")); - assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]")); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); + public void testFromLookupIndexForbidden() { + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", "FROM lookup-user1"), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "lookup-user1")) + ); + expectThrowsResponseException( + () -> runESQLCommand("metadata1_read2", "FROM lookup-first-alias"), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("metadata1_read2", "lookup-first-alias")) + ); + expectThrowsResponseException( + () -> runESQLCommand("alias_user1", "FROM lookup-user1"), + HttpStatus.SC_FORBIDDEN, + containsString(unauthorizedErrorMessage("alias_user1", "lookup-user1")) + ); } public void testListQueryAllowed() throws Exception { @@ -1215,4 +1234,14 @@ private void createDataStreamAlias() throws IOException { }"""); assertMap(entityAsMap(client().performRequest(request)), matchesMap().extraOk().entry("errors", false)); } + + private static void expectThrowsResponseException(ThrowingRunnable runnable, int status, Matcher messageMatcher) { + var exception = expectThrows(ResponseException.class, runnable); + assertThat(exception.getResponse().getStatusLine().getStatusCode(), equalTo(status)); + assertThat(exception.getMessage(), messageMatcher); + } + + private static String unauthorizedErrorMessage(String user, String index) { + return String.format("unauthorized for user [test-admin] run as [%s] with effective roles [%s] on indices [%s]", user, user, index); + } } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlRestValidationTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlRestValidationTestCase.java index 9ec4f60f4c843..9f40138679071 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlRestValidationTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlRestValidationTestCase.java @@ -38,7 +38,6 @@ public abstract class EsqlRestValidationTestCase extends ESRestTestCase { aliasName + "*,inexistent*", "inexistent*," + aliasName }; private static final String[] existentAliasWithoutWildcard = new String[] { aliasName + ",inexistent", "inexistent," + aliasName }; - private static final String[] inexistentIndexNameWithWildcard = new String[] { "inexistent*", "inexistent1*,inexistent2*" }; private static final String[] inexistentIndexNameWithoutWildcard = new String[] { "inexistent", "inexistent1,inexistent2" }; private static final String createAlias = "{\"actions\":[{\"add\":{\"index\":\"" + indexName + "\",\"alias\":\"" + aliasName + "\"}}]}"; private static final String removeAlias = "{\"actions\":[{\"remove\":{\"index\":\"" @@ -73,21 +72,20 @@ public void wipeTestData() throws IOException { } } - private String getInexistentIndexErrorMessage() { - return "\"reason\" : \"Found 1 problem\\nline 1:1: Unknown index "; - } - - public void testInexistentIndexNameWithWildcard() throws IOException { - assertErrorMessages(inexistentIndexNameWithWildcard, getInexistentIndexErrorMessage(), 400); - } - public void testInexistentIndexNameWithoutWildcard() throws IOException { - assertErrorMessages(inexistentIndexNameWithoutWildcard, getInexistentIndexErrorMessage(), 400); + for (String indexName : inexistentIndexNameWithoutWildcard) { + // TODO ES-13095 all from indexName should be reported as missing. + assertErrorMessage( + indexName, + "\"reason\" : \"Found 1 problem\\nline 1:1: Unknown index [" + clusterSpecificIndexName("inexistent"), + 400 + ); + } } public void testExistentIndexWithoutWildcard() throws IOException { for (String indexName : existentIndexWithoutWildcard) { - assertErrorMessage(indexName, "\"reason\" : \"no such index [inexistent]\"", 404); + assertErrorMessage(indexName, "\"reason\" : \"Found 1 problem\\nline 1:1: Unknown index [inexistent]\"", 400); } } @@ -99,19 +97,13 @@ public void testAlias() throws IOException { createAlias(); for (String indexName : existentAliasWithoutWildcard) { - assertErrorMessage(indexName, "\"reason\" : \"no such index [inexistent]\"", 404); + assertErrorMessage(indexName, "\"reason\" : \"Found 1 problem\\nline 1:1: Unknown index [inexistent]\"", 400); } assertValidRequestOnIndices(existentAliasWithWildcard); deleteAlias(); } - private void assertErrorMessages(String[] indices, String errorMessage, int statusCode) throws IOException { - for (String indexName : indices) { - assertErrorMessage(indexName, errorMessage + "[" + clusterSpecificIndexName(indexName) + "]", statusCode); - } - } - protected String clusterSpecificIndexName(String indexName) { return indexName; } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java index addbb6985818c..188eb9d83848d 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java @@ -85,15 +85,11 @@ public void testTimestampFilterFromQuery() throws IOException { allOf(instanceOf(List.class), hasSize(docsTest1)) ); - // filter excludes both indices (no rows); the first analysis step fails because there are no columns, a second attempt succeeds - // after eliminating the index filter. All columns are returned. + // filter excludes both indices (no rows); Empty result set is derived. builder = timestampFilter("gte", "2025-01-01").query(from("test*")); assertQueryResult( runEsql(builder), - matchesList().item(matchesMap().entry("name", "@timestamp").entry("type", "date")) - .item(matchesMap().entry("name", "id1").entry("type", "integer")) - .item(matchesMap().entry("name", "id2").entry("type", "integer")) - .item(matchesMap().entry("name", "value").entry("type", "long")), + matchesList().item(matchesMap().entry("name", "").entry("type", "null")), allOf(instanceOf(List.class), hasSize(0)) ); } @@ -213,22 +209,10 @@ public void testIndicesDontExist() throws IOException { ) ); - e = expectThrows(ResponseException.class, () -> runEsql(timestampFilter("gte", "2020-01-01").query(from("foo*")))); + e = expectThrows(ResponseException.class, () -> runEsql(timestampFilter("gte", "2020-01-01").query("FROM foo,test1"))); assertEquals(400, e.getResponse().getStatusLine().getStatusCode()); assertThat(e.getMessage(), containsString("verification_exception")); - assertThat( - e.getMessage(), - anyOf( - containsString("Unknown index [foo*]"), - containsString("Unknown index [*:foo*]"), - containsString("Unknown index [remote_cluster:foo*]") - ) - ); - - e = expectThrows(ResponseException.class, () -> runEsql(timestampFilter("gte", "2020-01-01").query("FROM foo, test1"))); - assertEquals(404, e.getResponse().getStatusLine().getStatusCode()); - assertThat(e.getMessage(), containsString("index_not_found_exception")); - assertThat(e.getMessage(), containsString("no such index [foo]")); + assertThat(e.getMessage(), containsString("Unknown index [foo]")); // Don't test remote patterns here, we'll test them in the multi-cluster tests if (EsqlCapabilities.Cap.JOIN_LOOKUP_V12.isEnabled()) { diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryIT.java index 1f7a116ee5b4f..1428e4d2c1f9c 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterQueryIT.java @@ -20,7 +20,6 @@ import org.elasticsearch.compute.operator.DriverProfile; import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; -import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.test.FailingFieldPlugin; @@ -133,15 +132,10 @@ public void testSearchesAgainstNonMatchingIndicesWithLocalOnly() throws Exceptio { String q = "FROM nomatch," + localIndex; - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> runQuery(q, false)); - assertThat(e.getDetailedMessage(), containsString("no such index [nomatch]")); - - // MP TODO: am I able to fix this from the field-caps call? Yes, if we detect concrete vs. wildcard expressions in user query - // TODO bug - this does not throw; uncomment this test once this is fixed: - // AwaitsFix https://github.com/elastic/elasticsearch/issues/114495 - // String limit0 = q + " | LIMIT 0"; - // VerificationException ve = expectThrows(VerificationException.class, () -> runQuery(limit0, false)); - // assertThat(ve.getDetailedMessage(), containsString("No matching indices for [nomatch]")); + expectThrows(VerificationException.class, containsString("Unknown index [nomatch]"), () -> runQuery(q, false)); + + String limit0 = q + " | LIMIT 0"; + expectThrows(VerificationException.class, containsString("Unknown index [nomatch]"), () -> runQuery(limit0, false)); } { @@ -168,11 +162,6 @@ public void testSearchesAgainstNonMatchingIndicesWithLocalOnly() throws Exceptio String expectedError = "Unknown index [nomatch]"; expectVerificationExceptionForQuery(q, expectedError, false); } - { - String q = "FROM nomatch*"; - String expectedError = "Unknown index [nomatch*]"; - expectVerificationExceptionForQuery(q, expectedError, false); - } } public void testSearchesAgainstIndicesWithNoMappingsSkipUnavailableTrue() throws Exception { @@ -234,10 +223,6 @@ public void testSearchesAgainstIndicesWithNoMappingsSkipUnavailableTrue() throws } public void testSearchesAgainstNonMatchingIndices() throws Exception { - testSearchesAgainstNonMatchingIndices(true); - } - - protected void testSearchesAgainstNonMatchingIndices(boolean exceptionWithSkipUnavailableFalse) throws Exception { int numClusters = 3; Map testClusterInfo = setupClusters(numClusters); int localNumShards = (Integer) testClusterInfo.get("local.num_shards"); @@ -264,15 +249,13 @@ protected void testSearchesAgainstNonMatchingIndices(boolean exceptionWithSkipUn { String q = "FROM logs*,cluster-a:nomatch"; String expectedError = "Unknown index [cluster-a:nomatch]"; - if (exceptionWithSkipUnavailableFalse) { - setSkipUnavailable(REMOTE_CLUSTER_1, false); - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); - setSkipUnavailable(REMOTE_CLUSTER_1, true); - } + setSkipUnavailable(REMOTE_CLUSTER_1, false); + expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + setSkipUnavailable(REMOTE_CLUSTER_1, true); try (EsqlQueryResponse resp = runQuery(q, requestIncludeMeta)) { assertThat(getValuesList(resp).size(), greaterThanOrEqualTo(1)); EsqlExecutionInfo executionInfo = resp.getExecutionInfo(); - assertThat(executionInfo.isCrossClusterSearch(), is(true)); + assertThat(resp.getExecutionInfo().isCrossClusterSearch(), is(true)); assertThat(executionInfo.includeCCSMetadata(), equalTo(responseExpectMeta)); assertExpectedClustersForMissingIndicesTests( executionInfo, @@ -366,41 +349,60 @@ protected void testSearchesAgainstNonMatchingIndices(boolean exceptionWithSkipUn } } - // an error is thrown if there is a concrete index that does not match - { - String q = "FROM cluster-a:nomatch"; - String expectedError = "Unknown index [cluster-a:nomatch]"; - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + // empty result with missing index and default skip_unavailable=true + try (EsqlQueryResponse response = runQuery("FROM cluster-a:nomatch", requestIncludeMeta)) { + assertThat(getValuesList(response).size(), equalTo(0)); + assertThat(response.columns().size(), greaterThan(0)); + assertExpectedClustersForMissingIndicesTests( + response.getExecutionInfo(), + List.of(new ExpectedCluster(REMOTE_CLUSTER_1, "nomatch", EsqlExecutionInfo.Cluster.Status.SKIPPED, 0)) + ); } - // an error is thrown if there are no matching indices at all - single remote cluster with wildcard index expression - { - String q = "FROM cluster-a:nomatch*"; - String expectedError = "Unknown index [cluster-a:nomatch*]"; - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + // empty result with an empty remote index pattern + try (EsqlQueryResponse response = runQuery("FROM cluster-a:nomatch*", requestIncludeMeta)) { + assertThat(getValuesList(response).size(), equalTo(0)); + assertThat(response.columns().size(), greaterThan(0)); + assertExpectedClustersForMissingIndicesTests( + response.getExecutionInfo(), + List.of(new ExpectedCluster(REMOTE_CLUSTER_1, "nomatch*", EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, 0)) + ); } - // an error is thrown if there is a concrete index that does not match - { - String q = "FROM nomatch*,cluster-a:nomatch"; - String expectedError = "Unknown index [nomatch*,cluster-a:nomatch]"; - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + // empty results with empty patterns and unresolved remote concrete index + try (EsqlQueryResponse response = runQuery("FROM nomatch*,cluster-a:nomatch", requestIncludeMeta)) { + assertThat(getValuesList(response).size(), equalTo(0)); + assertThat(response.columns().size(), greaterThan(0)); + assertExpectedClustersForMissingIndicesTests( + response.getExecutionInfo(), + List.of( + new ExpectedCluster(LOCAL_CLUSTER, "nomatch*", EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, 0), + new ExpectedCluster(REMOTE_CLUSTER_1, "nomatch", EsqlExecutionInfo.Cluster.Status.SKIPPED, 0) + ) + ); } - // an error is thrown if there are no matching indices at all - local with wildcard, remote with wildcard - { - String q = "FROM nomatch*,cluster-a:nomatch*"; - String expectedError = "Unknown index [nomatch*,cluster-a:nomatch*]"; - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + // empty result from empty local and empty remote pattern + try (EsqlQueryResponse response = runQuery("FROM nomatch*,cluster-a:nomatch*", requestIncludeMeta)) { + assertThat(getValuesList(response).size(), equalTo(0)); + assertThat(response.columns().size(), greaterThan(0)); + assertExpectedClustersForMissingIndicesTests( + response.getExecutionInfo(), + List.of( + new ExpectedCluster(LOCAL_CLUSTER, "nomatch*", EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, 0), + new ExpectedCluster(REMOTE_CLUSTER_1, "nomatch*", EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, 0) + ) + ); } + { String q = "FROM nomatch,cluster-a:nomatch"; - String expectedError = "Unknown index [nomatch,cluster-a:nomatch]"; + String expectedError = "Unknown index [nomatch]"; expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); } { String q = "FROM nomatch,cluster-a:nomatch*"; - String expectedError = "Unknown index [nomatch,cluster-a:nomatch*]"; + String expectedError = "Unknown index [nomatch]"; expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); } @@ -410,20 +412,23 @@ protected void testSearchesAgainstNonMatchingIndices(boolean exceptionWithSkipUn { String localIndexName = randomFrom(localIndex, IDX_ALIAS, FILTERED_IDX_ALIAS); String remote2IndexName = randomFrom(remote2Index, IDX_ALIAS, FILTERED_IDX_ALIAS); - String q = Strings.format("FROM %s*,cluster-a:nomatch,%s:%s*", localIndexName, REMOTE_CLUSTER_2, remote2IndexName); + + String q = "FROM " + + (localIndexName + "*,") // local index pattern + + (REMOTE_CLUSTER_1 + ":nomatch,") // missing remote index + + (REMOTE_CLUSTER_2 + ":" + remote2IndexName + "*"); // present remote pattern + + setSkipUnavailable(REMOTE_CLUSTER_1, false); String expectedError = "Unknown index [cluster-a:nomatch]"; - if (exceptionWithSkipUnavailableFalse) { - setSkipUnavailable(REMOTE_CLUSTER_1, false); - expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); - setSkipUnavailable(REMOTE_CLUSTER_1, true); - } + expectVerificationExceptionForQuery(q, expectedError, requestIncludeMeta); + + setSkipUnavailable(REMOTE_CLUSTER_1, true); try (EsqlQueryResponse resp = runQuery(q, requestIncludeMeta)) { assertThat(getValuesList(resp).size(), greaterThanOrEqualTo(1)); - EsqlExecutionInfo executionInfo = resp.getExecutionInfo(); - assertThat(executionInfo.isCrossClusterSearch(), is(true)); - assertThat(executionInfo.includeCCSMetadata(), equalTo(responseExpectMeta)); + assertThat(resp.getExecutionInfo().isCrossClusterSearch(), is(true)); + assertThat(resp.getExecutionInfo().includeCCSMetadata(), equalTo(responseExpectMeta)); assertExpectedClustersForMissingIndicesTests( - executionInfo, + resp.getExecutionInfo(), List.of( new ExpectedCluster( LOCAL_CLUSTER, @@ -445,19 +450,15 @@ protected void testSearchesAgainstNonMatchingIndices(boolean exceptionWithSkipUn } } - record ExpectedCluster(String clusterAlias, String indexExpression, EsqlExecutionInfo.Cluster.Status status, Integer totalShards) {} + record ExpectedCluster(String clusterAlias, String indexExpression, EsqlExecutionInfo.Cluster.Status status, int totalShards) {} /** * Runs the provided query, expecting a VerificationError. It then runs the same query with a "| LIMIT 0" * extra processing step to ensure that ESQL coordinator-only operations throw the same VerificationError. */ protected void expectVerificationExceptionForQuery(String query, String error, Boolean requestIncludeMeta) { - VerificationException e = expectThrows(VerificationException.class, () -> runQuery(query, requestIncludeMeta)); - assertThat(e.getDetailedMessage(), containsString(error)); - - String limit0 = query + " | LIMIT 0"; - e = expectThrows(VerificationException.class, () -> runQuery(limit0, requestIncludeMeta)); - assertThat(e.getDetailedMessage(), containsString(error)); + expectThrows(VerificationException.class, containsString(error), () -> runQuery(query, requestIncludeMeta)); + expectThrows(VerificationException.class, containsString(error), () -> runQuery(query + " | LIMIT 0", requestIncludeMeta)); } public void assertExpectedClustersForMissingIndicesTests(EsqlExecutionInfo executionInfo, List expected) { diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java index f50091e164086..27633c2d7daa2 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/IndexResolutionIT.java @@ -17,7 +17,6 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.datastreams.DataStreamsPlugin; -import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchNoneQueryBuilder; import org.elasticsearch.plugins.Plugin; @@ -112,23 +111,20 @@ public void testResolvesExclusionPattern() { assertOk(response); assertResultConcreteIndices(response, "index-1");// excludes pattern from pattern } - expectThrows( - VerificationException.class, - containsString("Unknown index [index-*,-*]"), - () -> run(syncEsqlQueryRequest().query("FROM index-*,-* METADATA _index")) // exclude all resolves to empty - ); + try (var response = run(syncEsqlQueryRequest().query("FROM index-*,-* METADATA _index"))) { + assertOk(response); + assertResultConcreteIndices(response); + } } public void testDoesNotResolveEmptyPattern() { assertAcked(client().admin().indices().prepareCreate("data")); indexRandom(true, "data", 1); - expectThrows( - VerificationException.class, - containsString("Unknown index [index-*]"), - () -> run(syncEsqlQueryRequest().query("FROM index-* METADATA _index")) - ); - + try (var response = run(syncEsqlQueryRequest().query("FROM index-* METADATA _index"))) { + assertOk(response); + assertResultConcreteIndices(response); + } try (var response = run(syncEsqlQueryRequest().query("FROM data,index-* METADATA _index"))) { assertOk(response); assertResultConcreteIndices(response, "data"); @@ -224,21 +220,20 @@ public void testUnavailableIndex() { public void testPartialResolution() { assertAcked(client().admin().indices().prepareCreate("index-1")); - assertAcked(client().admin().indices().prepareCreate("index-2")); - indexRandom(true, "index-2", 10); - - try (var response = run(syncEsqlQueryRequest().query("FROM index-1,nonexisting-1"))) { - assertOk(response); // okay when present index is empty + if (randomBoolean()) { + indexRandom(true, "index-1", 1); } + expectThrows( - IndexNotFoundException.class, - equalTo("no such index [nonexisting-1]"), // fails when present index is non-empty - () -> run(syncEsqlQueryRequest().query("FROM index-2,nonexisting-1")) + VerificationException.class, + containsString("Unknown index [nonexisting-1]"), + () -> run(syncEsqlQueryRequest().query("FROM index-1,nonexisting-1")) ); expectThrows( - IndexNotFoundException.class, - equalTo("no such index [nonexisting-1]"), // only the first missing index is reported - () -> run(syncEsqlQueryRequest().query("FROM index-2,nonexisting-1,nonexisting-2")) + VerificationException.class, + // TODO ES-13095 both [nonexisting-1,nonexisting-2] missing should be reported as missing. + containsString("Unknown index [nonexisting-1]"), // only the first missing index is reported + () -> run(syncEsqlQueryRequest().query("FROM index-1,nonexisting-1,nonexisting-2")) ); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResolveFieldsAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResolveFieldsAction.java index 47574a6530837..77ed5530e7f4c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResolveFieldsAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResolveFieldsAction.java @@ -6,7 +6,6 @@ */ package org.elasticsearch.xpack.esql.action; -import org.elasticsearch.ElasticsearchTimeoutException; import org.elasticsearch.TransportVersion; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListenerResponseHandler; @@ -138,13 +137,10 @@ private void doExecuteForked(Task task, FieldCapabilitiesRequest request, Action final Map remoteClusterIndices = transportService.getRemoteClusterService() .groupIndices(request.indicesOptions(), request.indices(), request.returnLocalAll()); final OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY); - final String[] concreteIndices; - if (localIndices == null) { - // in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices - concreteIndices = Strings.EMPTY_ARRAY; - } else { - concreteIndices = indexNameExpressionResolver.concreteIndexNames(projectState.metadata(), localIndices); - } + // in the case we have one or more remote indices but no local we don't expand to all local indices and just do remote indices + final String[] concreteIndices = localIndices == null + ? Strings.EMPTY_ARRAY + : indexNameExpressionResolver.concreteIndexNames(projectState.metadata(), localIndices); if (concreteIndices.length == 0 && remoteClusterIndices.isEmpty()) { // No indices at all! @@ -334,28 +330,6 @@ private static void finishHim( ActionListener listener ) { List failures = indexFailures.build(indexResponses.keySet()); - if (indexResponses.isEmpty() == false) { - listener.onResponse(new FieldCapabilitiesResponse(new ArrayList<>(indexResponses.values()), failures)); - } else { - // we have no responses at all, maybe because of errors - if (indexFailures.isEmpty() == false) { - /* - * Under no circumstances are we to pass timeout errors originating from SubscribableListener as top-level errors. - * Instead, they should always be passed through the response object, as part of "failures". - */ - if (failures.stream() - .anyMatch( - failure -> failure.getException() instanceof IllegalStateException ise - && ise.getCause() instanceof ElasticsearchTimeoutException - )) { - listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), failures)); - } else { - // throw back the first exception - listener.onFailure(failures.get(0).getException()); - } - } else { - listener.onResponse(new FieldCapabilitiesResponse(Collections.emptyList(), Collections.emptyList())); - } - } + listener.onResponse(new FieldCapabilitiesResponse(new ArrayList<>(indexResponses.values()), failures)); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java index 0744fd126999d..7d834989b5801 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/EnrichPolicyResolver.java @@ -451,6 +451,7 @@ public void messageReceived(LookupRequest request, TransportChannel channel, Tas indexName, IndexResolver.ALL_FIELDS, null, + clusterAlias -> false, false, // Disable aggregate_metric_double and dense_vector until we get version checks in planning false, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java index 4d31f48da77de..68033ee58413c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/index/IndexResolution.java @@ -41,9 +41,13 @@ public static IndexResolution invalid(String invalid) { return new IndexResolution(null, invalid, Set.of(), Map.of()); } - public static IndexResolution notFound(String name) { - Objects.requireNonNull(name, "name must not be null"); - return invalid("Unknown index [" + name + "]"); + public static IndexResolution empty(String indexPattern) { + return valid(new EsIndex(indexPattern, Map.of(), Map.of())); + } + + public static IndexResolution notFound(String indices) { + Objects.requireNonNull(indices, "indices must not be null"); + return invalid("Unknown index [" + indices + "]"); } private final EsIndex index; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java index ded34107f5aec..e79736d6b0b9a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java @@ -509,6 +509,7 @@ private void preAnalyzeLookupIndex( EsqlCCSUtils.createQualifiedLookupIndexExpressionFromAvailableClusters(executionInfo, localPattern), result.wildcardJoinIndices().contains(localPattern) ? IndexResolver.ALL_FIELDS : result.fieldNames, null, + executionInfo::shouldSkipOnFailure, false, // Disable aggregate_metric_double and dense_vector until we get version checks in planning false, @@ -710,9 +711,7 @@ private void preAnalyzeMainIndices( if (preAnalysis.indexPattern() != null) { if (executionInfo.clusterAliases().isEmpty()) { // return empty resolution if the expression is pure CCS and resolved no remote clusters (like no-such-cluster*:index) - listener.onResponse( - result.withIndices(IndexResolution.valid(new EsIndex(preAnalysis.indexPattern().indexPattern(), Map.of(), Map.of()))) - ); + listener.onResponse(result.withIndices(IndexResolution.empty(preAnalysis.indexPattern().indexPattern()))); } else { indexResolver.resolveAsMergedMapping( preAnalysis.indexPattern().indexPattern(), @@ -727,6 +726,7 @@ private void preAnalyzeMainIndices( } default -> requestFilter; }, + executionInfo::shouldSkipOnFailure, preAnalysis.indexMode() == IndexMode.TIME_SERIES, preAnalysis.supportsAggregateMetricDouble(), preAnalysis.supportsDenseVector(), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index 20936e6fa86f4..66a69648333b7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -6,7 +6,9 @@ */ package org.elasticsearch.xpack.esql.session; +import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.fieldcaps.FieldCapabilitiesFailure; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesIndexResponse; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; @@ -16,11 +18,13 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.Maps; import org.elasticsearch.index.IndexMode; +import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.RemoteClusterAware; import org.elasticsearch.xpack.esql.action.EsqlResolveFieldsAction; import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -43,6 +47,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.Predicate; import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; @@ -53,14 +58,15 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; public class IndexResolver { - private static Logger LOGGER = LogManager.getLogger(IndexResolver.class); + + private static final Logger LOGGER = LogManager.getLogger(IndexResolver.class); public static final Set ALL_FIELDS = Set.of("*"); public static final Set INDEX_METADATA_FIELD = Set.of(MetadataAttribute.INDEX); public static final String UNMAPPED = "unmapped"; public static final IndicesOptions FIELD_CAPS_INDICES_OPTIONS = IndicesOptions.builder() - .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ALLOW_UNAVAILABLE_TARGETS) + .concreteTargetOptions(IndicesOptions.ConcreteTargetOptions.ERROR_WHEN_UNAVAILABLE_TARGETS) .wildcardOptions( IndicesOptions.WildcardOptions.builder() .matchOpen(true) @@ -87,6 +93,7 @@ public void resolveAsMergedMapping( String indexWildcard, Set fieldNames, QueryBuilder requestFilter, + Predicate canSkip, boolean includeAllDimensions, boolean supportsAggregateMetricDouble, boolean supportsDenseVector, @@ -95,11 +102,36 @@ public void resolveAsMergedMapping( client.execute( EsqlResolveFieldsAction.TYPE, createFieldCapsRequest(indexWildcard, fieldNames, requestFilter, includeAllDimensions), - listener.delegateFailureAndWrap((l, response) -> { + ActionListener.wrap(response -> { LOGGER.debug("minimum transport version {}", response.minTransportVersion()); - l.onResponse( - mergedMappings(indexWildcard, new FieldsInfo(response.caps(), supportsAggregateMetricDouble, supportsDenseVector)) - ); + var missingIndices = new TreeSet(); + for (FieldCapabilitiesFailure failure : response.caps().getFailures()) { + var cause = ExceptionsHelper.unwrapCause(failure.getException()); + var clusterAlias = RemoteClusterAware.parseClusterAlias(failure.getIndices()[0]); + if (canSkip.test(clusterAlias)) { + continue; + } + if (cause instanceof IndexNotFoundException) { + missingIndices.addAll(Arrays.asList(failure.getIndices())); + } else { + listener.onFailure(failure.getException()); + return; + } + } + if (missingIndices.isEmpty()) { + listener.onResponse( + mergedMappings(indexWildcard, new FieldsInfo(response.caps(), supportsAggregateMetricDouble, supportsDenseVector)) + ); + } else { + listener.onResponse(IndexResolution.notFound(String.join(",", missingIndices))); + } + }, failure -> { + var cause = ExceptionsHelper.unwrapCause(failure); + if (cause instanceof IndexNotFoundException e) { + listener.onResponse(IndexResolution.notFound(e.getIndex().getName())); + } else { + listener.onFailure(failure); + } }) ); } @@ -111,7 +143,7 @@ public static IndexResolution mergedMappings(String indexPattern, FieldsInfo fie assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SEARCH_COORDINATION); // too expensive to run this on a transport worker int numberOfIndices = fieldsInfo.caps.getIndexResponses().size(); if (numberOfIndices == 0) { - return IndexResolution.notFound(indexPattern); + return IndexResolution.empty(indexPattern); } // For each field name, store a list of the field caps responses from each index diff --git a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java index 361f5e401eaf5..4689d398bb4dc 100644 --- a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java +++ b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java @@ -1145,7 +1145,7 @@ public void testFailureStoreAccess() throws Exception { request, containsString("this action is granted by the index privileges [read,all]") ); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; default: fail("must cover user: " + user); @@ -1181,7 +1181,7 @@ public void testFailureStoreAccess() throws Exception { case FAILURE_STORE_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; default: fail("must cover user: " + user); @@ -1217,7 +1217,7 @@ public void testFailureStoreAccess() throws Exception { case FAILURE_STORE_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user); @@ -1235,7 +1235,7 @@ public void testFailureStoreAccess() throws Exception { case FAILURE_STORE_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user); @@ -1256,7 +1256,10 @@ public void testFailureStoreAccess() throws Exception { case FAILURE_STORE_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, BACKING_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + // es|ql does not support _all or empty target + if (request.searchTarget.equals("*")) { + expectEsql(user, request); + } break; default: fail("must cover user: " + user); @@ -1273,7 +1276,7 @@ public void testFailureStoreAccess() throws Exception { break; case FAILURE_STORE_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user); @@ -1290,7 +1293,7 @@ public void testFailureStoreAccess() throws Exception { break; case FAILURE_STORE_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; default: fail("must cover user: " + user); @@ -1325,7 +1328,7 @@ public void testFailureStoreAccess() throws Exception { case FAILURE_STORE_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; default: fail("must cover user: " + user); @@ -1364,7 +1367,7 @@ public void testFailureStoreAccess() throws Exception { + "or by [read_failure_store] for access with the [::failures] selector" ) ); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearch(user, request, failuresDocId); @@ -1400,7 +1403,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearch(user, request, failuresDocId); @@ -1436,7 +1439,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearch(user, request, failuresDocId); @@ -1454,7 +1457,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearch(user, request, failuresDocId); @@ -1472,7 +1475,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearch(user, request, failuresDocId); @@ -1489,7 +1492,7 @@ public void testFailureStoreAccess() throws Exception { switch (user) { case DATA_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; case ADMIN_USER, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS, BOTH_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request, failuresDocId); @@ -1506,7 +1509,7 @@ public void testFailureStoreAccess() throws Exception { switch (user) { case DATA_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case ADMIN_USER, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS, BOTH_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request, failuresDocId); @@ -1541,7 +1544,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case FAILURE_STORE_ACCESS, BOTH_ACCESS, ADMIN_USER, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 404); @@ -1573,7 +1576,7 @@ public void testFailureStoreAccess() throws Exception { case STAR_READ_ONLY_ACCESS, BOTH_ACCESS, DATA_ACCESS, FAILURE_STORE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, BACKING_INDEX_DATA_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case ADMIN_USER, BACKING_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 404); @@ -1605,7 +1608,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, FAILURE_STORE_ACCESS, ADMIN_USER, STAR_READ_ONLY_ACCESS, BOTH_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user); @@ -1619,7 +1622,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, FAILURE_STORE_ACCESS, ADMIN_USER, STAR_READ_ONLY_ACCESS, BOTH_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user); @@ -1633,7 +1636,7 @@ public void testFailureStoreAccess() throws Exception { case DATA_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); + expectEsqlThrows(user, request, 403); break; case ADMIN_USER, FAILURE_STORE_ACCESS, BOTH_ACCESS: expectSearchThrows(user, request, 404); @@ -1664,14 +1667,11 @@ public void testFailureStoreAccess() throws Exception { var request = new Search("test1,test1::failures"); for (var user : users) { switch (user) { - case DATA_ACCESS, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS: + case DATA_ACCESS, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, + FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); expectEsqlThrows(user, request, 403); break; - case BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: - expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); - break; case ADMIN_USER, BOTH_ACCESS: expectSearch(user, request, dataDocId, failuresDocId); expectEsql(user, request, dataDocId, failuresDocId); @@ -1710,14 +1710,11 @@ public void testFailureStoreAccess() throws Exception { var request = new Search("test1," + failureIndexName); for (var user : users) { switch (user) { - case DATA_ACCESS, FAILURE_STORE_ACCESS, FAILURE_INDEX_DATA_ACCESS: + case DATA_ACCESS, FAILURE_STORE_ACCESS, FAILURE_INDEX_DATA_ACCESS, BACKING_INDEX_DATA_ACCESS, + FAILURE_INDEX_FAILURE_ACCESS, BACKING_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); expectEsqlThrows(user, request, 403); break; - case BACKING_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, BACKING_INDEX_FAILURE_ACCESS: - expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); - break; case ADMIN_USER, BOTH_ACCESS, STAR_READ_ONLY_ACCESS: expectSearch(user, request, dataDocId, failuresDocId); expectEsql(user, request, dataDocId, failuresDocId); @@ -1755,14 +1752,11 @@ public void testFailureStoreAccess() throws Exception { var request = new Search("test1::failures," + dataIndexName); for (var user : users) { switch (user) { - case DATA_ACCESS, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS: + case DATA_ACCESS, FAILURE_STORE_ACCESS, STAR_READ_ONLY_ACCESS, BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, + FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); expectEsqlThrows(user, request, 403); break; - case BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: - expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); - break; case ADMIN_USER, BOTH_ACCESS: expectSearch(user, request, dataDocId, failuresDocId); expectEsql(user, request, dataDocId, failuresDocId); @@ -1801,14 +1795,11 @@ public void testFailureStoreAccess() throws Exception { var request = new Search("test1,*::failures"); for (var user : users) { switch (user) { - case FAILURE_STORE_ACCESS: + case FAILURE_STORE_ACCESS, BACKING_INDEX_DATA_ACCESS, FAILURE_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, + FAILURE_INDEX_FAILURE_ACCESS: expectSearchThrows(user, request, 403); expectEsqlThrows(user, request, 403); break; - case BACKING_INDEX_DATA_ACCESS, FAILURE_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: - expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); - break; case DATA_ACCESS, STAR_READ_ONLY_ACCESS: expectSearch(user, request, dataDocId); expectEsql(user, request, dataDocId); @@ -1855,11 +1846,8 @@ public void testFailureStoreAccess() throws Exception { expectSearch(user, request, failuresDocId); expectEsql(user, request, failuresDocId); break; - case BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS: - expectSearchThrows(user, request, 403); - expectEsqlThrows(user, request, 400); - break; - case DATA_ACCESS, STAR_READ_ONLY_ACCESS: + case BACKING_INDEX_DATA_ACCESS, BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_DATA_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, + DATA_ACCESS, STAR_READ_ONLY_ACCESS: expectSearchThrows(user, request, 403); expectEsqlThrows(user, request, 403); break; @@ -1915,7 +1903,7 @@ public void testFailureStoreAccess() throws Exception { break; case BACKING_INDEX_FAILURE_ACCESS, FAILURE_INDEX_FAILURE_ACCESS, BACKING_INDEX_DATA_ACCESS, FAILURE_INDEX_DATA_ACCESS: expectSearch(user, request); - expectEsqlThrows(user, request, 400); + expectEsql(user, request); break; default: fail("must cover user: " + user);