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 6494a29e83075..73cd7b77968fe 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,7 +27,6 @@ import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; -import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; import org.junit.Before; import org.junit.ClassRule; @@ -40,6 +39,7 @@ import static org.elasticsearch.test.ListMatcher.matchesList; import static org.elasticsearch.test.MapMatcher.assertMap; import static org.elasticsearch.test.MapMatcher.matchesMap; +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -594,7 +594,7 @@ record Listen(long timestamp, String songId, double duration) { public void testLookupJoinIndexAllowed() throws Exception { assumeTrue( "Requires LOOKUP JOIN capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) + hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) ); Response resp = runESQLCommand( @@ -685,7 +685,7 @@ public void testLookupJoinIndexAllowed() throws Exception { public void testLookupJoinDocLevelSecurity() throws Exception { assumeTrue( "Requires LOOKUP JOIN capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) + hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) ); Response resp = runESQLCommand("dls_user", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"); @@ -734,7 +734,7 @@ public void testLookupJoinDocLevelSecurity() throws Exception { public void testLookupJoinFieldLevelSecurity() throws Exception { assumeTrue( "Requires LOOKUP JOIN capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) + hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) ); Response resp = runESQLCommand("fls_user2", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value"); @@ -792,7 +792,7 @@ public void testLookupJoinFieldLevelSecurity() throws Exception { public void testLookupJoinFieldLevelSecurityOnAlias() throws Exception { assumeTrue( "Requires LOOKUP JOIN capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) + hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) ); Response resp = runESQLCommand("fls_user2_alias", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value"); @@ -850,7 +850,7 @@ public void testLookupJoinFieldLevelSecurityOnAlias() throws Exception { public void testLookupJoinIndexForbidden() throws Exception { assumeTrue( "Requires LOOKUP JOIN capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) + hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) ); var resp = expectThrows( diff --git a/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java b/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java index 5578eff5cc67a..9695411b2c33b 100644 --- a/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java +++ b/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java @@ -12,7 +12,6 @@ import org.elasticsearch.test.rest.TestFeatureService; import org.elasticsearch.xpack.esql.CsvSpecReader.CsvTestCase; import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import org.junit.AfterClass; import org.junit.Before; import org.junit.ClassRule; @@ -22,6 +21,7 @@ import static org.elasticsearch.xpack.esql.CsvTestUtils.isEnabled; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_LOOKUP_V12; +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; public class MixedClusterEsqlSpecIT extends EsqlSpecTestCase { @ClassRule @@ -43,11 +43,6 @@ public void extractOldClusterFeatures() { } } - protected static boolean oldClusterHasFeature(String featureId) { - assert oldClusterTestFeatureService != null; - return oldClusterTestFeatureService.clusterHasFeature(featureId); - } - @AfterClass public static void cleanUp() { oldClusterTestFeatureService = null; @@ -59,10 +54,9 @@ public MixedClusterEsqlSpecIT( String testName, Integer lineNumber, CsvTestCase testCase, - String instructions, - Mode mode + String instructions ) { - super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + super(fileName, groupName, testName, lineNumber, testCase, instructions); } @Override @@ -87,12 +81,12 @@ protected boolean supportsInferenceTestService() { } @Override - protected boolean supportsIndexModeLookup() throws IOException { - return hasCapabilities(List.of(JOIN_LOOKUP_V12.capabilityName())); + protected boolean supportsIndexModeLookup() { + return hasCapabilities(adminClient(), List.of(JOIN_LOOKUP_V12.capabilityName())); } @Override - protected boolean supportsSourceFieldMapping() throws IOException { + protected boolean supportsSourceFieldMapping() { return false; } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java index dfb653b5e0941..38885d4a3263f 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java @@ -27,7 +27,6 @@ import org.elasticsearch.xpack.esql.CsvTestsDataLoader; import org.elasticsearch.xpack.esql.SpecReader; import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import org.junit.AfterClass; import org.junit.ClassRule; import org.junit.rules.RuleChain; @@ -36,7 +35,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URL; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -58,7 +56,7 @@ import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_PLANNING_V1; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.METADATA_FIELDS_REMOTE_TEST; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.UNMAPPED_FIELDS; -import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode.SYNC; +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -86,19 +84,7 @@ public class MultiClusterSpecIT extends EsqlSpecTestCase { public static List readScriptSpec() throws Exception { List urls = classpathResources("/*.csv-spec"); assertTrue("Not enough specs found " + urls, urls.size() > 0); - List specs = SpecReader.readScriptSpec(urls, specParser()); - - int len = specs.get(0).length; - List testcases = new ArrayList<>(); - for (var spec : specs) { - for (Mode mode : List.of(SYNC)) { // No async, for now - Object[] obj = new Object[len + 1]; - System.arraycopy(spec, 0, obj, 0, len); - obj[len] = mode; - testcases.add(obj); - } - } - return testcases; + return SpecReader.readScriptSpec(urls, specParser()); } public MultiClusterSpecIT( @@ -107,10 +93,9 @@ public MultiClusterSpecIT( String testName, Integer lineNumber, CsvTestCase testCase, - String instructions, - Mode mode + String instructions ) { - super(fileName, groupName, testName, lineNumber, convertToRemoteIndices(testCase), instructions, mode); + super(fileName, groupName, testName, lineNumber, convertToRemoteIndices(testCase), instructions); } // TODO: think how to handle this better @@ -152,7 +137,10 @@ protected void shouldSkipTest(String testName) throws IOException { assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(JOIN_PLANNING_V1.capabilityName())); assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V9.capabilityName())); if (testCase.requiredCapabilities.contains(JOIN_LOOKUP_V12.capabilityName())) { - assumeTrue("LOOKUP JOIN not yet supported in CCS", hasCapabilities(List.of(ENABLE_LOOKUP_JOIN_ON_REMOTE.capabilityName()))); + assumeTrue( + "LOOKUP JOIN not yet supported in CCS", + hasCapabilities(adminClient(), List.of(ENABLE_LOOKUP_JOIN_ON_REMOTE.capabilityName())) + ); } // Unmapped fields require a coorect capability response from every cluster, which isn't currently implemented. assumeFalse("UNMAPPED FIELDS not yet supported in CCS", testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName())); @@ -401,7 +389,7 @@ protected boolean supportsInferenceTestService() { @Override protected boolean supportsIndexModeLookup() throws IOException { - return hasCapabilities(List.of(JOIN_LOOKUP_V12.capabilityName())); + return hasCapabilities(adminClient(), List.of(JOIN_LOOKUP_V12.capabilityName())); } @Override diff --git a/x-pack/plugin/esql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/multi_node/EsqlSpecIT.java b/x-pack/plugin/esql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/multi_node/EsqlSpecIT.java index ffdf0eb336a1a..e36bd451c8298 100644 --- a/x-pack/plugin/esql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/multi_node/EsqlSpecIT.java +++ b/x-pack/plugin/esql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/multi_node/EsqlSpecIT.java @@ -10,7 +10,6 @@ import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.xpack.esql.CsvSpecReader.CsvTestCase; import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import org.junit.ClassRule; import java.io.IOException; @@ -24,16 +23,8 @@ protected String getTestRestCluster() { return cluster.getHttpAddresses(); } - public EsqlSpecIT( - String fileName, - String groupName, - String testName, - Integer lineNumber, - CsvTestCase testCase, - String instructions, - Mode mode - ) { - super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + public EsqlSpecIT(String fileName, String groupName, String testName, Integer lineNumber, CsvTestCase testCase, String instructions) { + super(fileName, groupName, testName, lineNumber, testCase, instructions); } @Override diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/EsqlSpecIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/EsqlSpecIT.java index 4c4ce020eb773..f4ec3f099e068 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/EsqlSpecIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/EsqlSpecIT.java @@ -19,7 +19,6 @@ import org.elasticsearch.xpack.esql.planner.PhysicalSettings; import org.elasticsearch.xpack.esql.plugin.ComputeService; import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import org.junit.Before; import org.junit.ClassRule; @@ -37,16 +36,8 @@ protected String getTestRestCluster() { return cluster.getHttpAddresses(); } - public EsqlSpecIT( - String fileName, - String groupName, - String testName, - Integer lineNumber, - CsvTestCase testCase, - String instructions, - Mode mode - ) { - super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + public EsqlSpecIT(String fileName, String groupName, String testName, Integer lineNumber, CsvTestCase testCase, String instructions) { + super(fileName, groupName, testName, lineNumber, testCase, instructions); } @Override diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java index cb1f4383600d8..a2ddb87cf0f44 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java @@ -12,7 +12,6 @@ import org.elasticsearch.test.TestClustersThreadFilter; import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.xpack.esql.CsvSpecReader; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeForkRestTest; import org.junit.ClassRule; @@ -32,10 +31,9 @@ public GenerativeForkIT( String testName, Integer lineNumber, CsvSpecReader.CsvTestCase testCase, - String instructions, - Mode mode + String instructions ) { - super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + super(fileName, groupName, testName, lineNumber, testCase, instructions); } @Override diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java index fa4f5a4b3909f..5ca7e4e8b03aa 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java @@ -68,7 +68,6 @@ import static org.elasticsearch.xpack.esql.CsvTestUtils.ExpectedResults; import static org.elasticsearch.xpack.esql.CsvTestUtils.isEnabled; import static org.elasticsearch.xpack.esql.CsvTestUtils.loadCsvSpecValues; -import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.availableDatasetsForEs; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.createInferenceEndpoints; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.deleteInferenceEndpoints; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadDataSetIntoEs; @@ -79,6 +78,7 @@ import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.SEMANTIC_TEXT_FIELD_CAPS; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.SOURCE_FIELD_MAPPING; import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.assertNotPartial; +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; // This test can run very long in serverless configurations @TimeoutSuite(millis = 30 * TimeUnits.MINUTE) @@ -101,19 +101,7 @@ public abstract class EsqlSpecTestCase extends ESRestTestCase { public static List readScriptSpec() throws Exception { List urls = classpathResources("/*.csv-spec"); assertTrue("Not enough specs found " + urls, urls.size() > 0); - List specs = SpecReader.readScriptSpec(urls, specParser()); - - int len = specs.get(0).length; - List testcases = new ArrayList<>(); - for (var spec : specs) { - for (Mode mode : Mode.values()) { - Object[] obj = new Object[len + 1]; - System.arraycopy(spec, 0, obj, 0, len); - obj[len] = mode; - testcases.add(obj); - } - } - return testcases; + return SpecReader.readScriptSpec(urls, specParser()); } protected EsqlSpecTestCase( @@ -122,8 +110,7 @@ protected EsqlSpecTestCase( String testName, Integer lineNumber, CsvTestCase testCase, - String instructions, - Mode mode + String instructions ) { this.fileName = fileName; this.groupName = groupName; @@ -131,25 +118,30 @@ protected EsqlSpecTestCase( this.lineNumber = lineNumber; this.testCase = testCase; this.instructions = instructions; - this.mode = mode; + this.mode = randomFrom(Mode.values()); } + private static boolean dataLoaded = false; + @Before public void setup() throws IOException { - if (supportsInferenceTestService()) { - createInferenceEndpoints(adminClient()); - } - boolean supportsLookup = supportsIndexModeLookup(); boolean supportsSourceMapping = supportsSourceFieldMapping(); - if (indexExists(availableDatasetsForEs(client(), supportsLookup, supportsSourceMapping).iterator().next().indexName()) == false) { - loadDataSetIntoEs(client(), supportsLookup, supportsSourceMapping); + boolean supportsInferenceTestService = supportsInferenceTestService(); + if (dataLoaded == false) { + if (supportsInferenceTestService) { + createInferenceEndpoints(adminClient()); + } + + loadDataSetIntoEs(client(), supportsLookup, supportsSourceMapping, supportsInferenceTestService); + dataLoaded = true; } } @AfterClass public static void wipeTestData() throws IOException { try { + dataLoaded = false; adminClient().performRequest(new Request("DELETE", "/*")); } catch (ResponseException e) { // 404 here just means we had no indexes @@ -159,7 +151,6 @@ public static void wipeTestData() throws IOException { } deleteInferenceEndpoints(adminClient()); - } public boolean logResults() { @@ -196,8 +187,12 @@ protected boolean supportTimeSeriesCommand() { return true; } - protected static void checkCapabilities(RestClient client, TestFeatureService testFeatureService, String testName, CsvTestCase testCase) - throws IOException { + protected static void checkCapabilities( + RestClient client, + TestFeatureService testFeatureService, + String testName, + CsvTestCase testCase + ) { if (hasCapabilities(client, testCase.requiredCapabilities)) { return; } @@ -211,38 +206,6 @@ protected static void checkCapabilities(RestClient client, TestFeatureService te } } - protected static boolean hasCapabilities(List requiredCapabilities) throws IOException { - return hasCapabilities(adminClient(), requiredCapabilities); - } - - public static boolean hasCapabilities(RestClient client, List requiredCapabilities) throws IOException { - if (requiredCapabilities.isEmpty()) { - return true; - } - try { - if (clusterHasCapability(client, "POST", "/_query", List.of(), requiredCapabilities).orElse(false)) { - return true; - } - LOGGER.info("capabilities API returned false, we might be in a mixed version cluster so falling back to cluster features"); - } catch (ResponseException e) { - if (e.getResponse().getStatusLine().getStatusCode() / 100 == 4) { - /* - * The node we're testing against is too old for the capabilities - * API which means it has to be pretty old. Very old capabilities - * are ALSO present in the features API, so we can check them instead. - * - * It's kind of weird that we check for *any* 400, but that's required - * because old versions of Elasticsearch return 400, not the expected - * 404. - */ - LOGGER.info("capabilities API failed, falling back to cluster features"); - } else { - throw e; - } - } - return false; - } - protected boolean supportsInferenceTestService() { return true; } @@ -271,7 +234,9 @@ protected final void doTest(String query) throws Throwable { builder.tables(tables()); } - Map prevTooks = supportsTook() ? tooks() : null; + boolean checkTook = supportsTook() && rarely(); + + Map prevTooks = checkTook ? tooks() : null; Map answer = RestEsqlTestCase.runEsql( builder.query(query), testCase.assertWarnings(deduplicateExactWarnings()), @@ -296,7 +261,7 @@ protected final void doTest(String query) throws Throwable { assertResults(expectedColumnsWithValues, actualColumns, actualValues, testCase.ignoreOrder, logger); - if (supportsTook()) { + if (checkTook) { LOGGER.info("checking took incremented from {}", prevTooks); long took = ((Number) answer.get("took")).longValue(); int prevTookHisto = ((Number) prevTooks.remove(tookKey(took))).intValue(); @@ -413,7 +378,6 @@ protected boolean preserveClusterUponCompletion() { return true; } - @Before @After public void assertRequestBreakerEmptyAfterTests() throws Exception { assertRequestBreakerEmpty(); @@ -421,7 +385,7 @@ public void assertRequestBreakerEmptyAfterTests() throws Exception { public static void assertRequestBreakerEmpty() throws Exception { assertBusy(() -> { - HttpEntity entity = adminClient().performRequest(new Request("GET", "/_nodes/stats")).getEntity(); + HttpEntity entity = adminClient().performRequest(new Request("GET", "/_nodes/stats?metric=breaker")).getEntity(); Map stats = XContentHelper.convertToMap(XContentType.JSON.xContent(), entity.getContent(), false); Map nodes = (Map) stats.get("nodes"); @@ -495,7 +459,7 @@ private Map> tables() { protected boolean supportsTook() throws IOException { if (supportsTook == null) { - supportsTook = hasCapabilities(client(), List.of("usage_contains_took")); + supportsTook = hasCapabilities(adminClient(), List.of("usage_contains_took")); } return supportsTook; } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java index d191cfcafa80f..83607c259c520 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java @@ -16,6 +16,7 @@ import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; import org.elasticsearch.client.WarningsHandler; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.io.Streams; @@ -42,6 +43,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.util.ArrayList; @@ -52,6 +54,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.IntFunction; import static java.util.Collections.emptySet; @@ -1334,8 +1338,8 @@ public static Map runEsqlAsync( checkKeepOnCompletion(requestObject, json, keepOnCompletion); String id = (String) json.get("id"); - var supportsAsyncHeaders = clusterHasCapability("POST", "/_query", List.of(), List.of("async_query_status_headers")).orElse(false); - var supportsSuggestedCast = clusterHasCapability("POST", "/_query", List.of(), List.of("suggested_cast")).orElse(false); + var supportsAsyncHeaders = hasCapabilities(adminClient(), List.of("async_query_status_headers")); + var supportsSuggestedCast = hasCapabilities(adminClient(), List.of("suggested_cast")); if (id == null) { // no id returned from an async call, must have completed immediately and without keep_on_completion @@ -1409,13 +1413,33 @@ public static Map runEsqlAsync( private static void prepareProfileLogger(RequestObjectBuilder requestObject, @Nullable ProfileLogger profileLogger) throws IOException { if (profileLogger != null) { profileLogger.clearProfile(); - var isProfileSafe = clusterHasCapability("POST", "/_query", List.of(), List.of("fixed_profile_serialization")).orElse(false); + var isProfileSafe = hasCapabilities(adminClient(), List.of("fixed_profile_serialization")); if (isProfileSafe) { requestObject.profile(true); } } } + record CapabilitesCacheKey(RestClient client, List capabilities) {} + + /** + * Cache of capabilities. + */ + private static final ConcurrentMap capabilities = new ConcurrentHashMap<>(); + + public static boolean hasCapabilities(RestClient client, List requiredCapabilities) { + if (requiredCapabilities.isEmpty()) { + return true; + } + return capabilities.computeIfAbsent(new CapabilitesCacheKey(client, requiredCapabilities), r -> { + try { + return clusterHasCapability(client, "POST", "/_query", List.of(), requiredCapabilities).orElse(false); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + private static Object removeOriginalTypesAndSuggestedCast(Object response) { if (response instanceof ArrayList columns) { var newColumns = new ArrayList<>(); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestRerankTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestRerankTestCase.java index aa4e7dc854c1c..7add36cbc2442 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestRerankTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestRerankTestCase.java @@ -33,7 +33,7 @@ public class RestRerankTestCase extends ESRestTestCase { public void skipWhenRerankDisabled() throws IOException { assumeTrue( "Requires RERANK capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.RERANK.capabilityName())) + RestEsqlTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.RERANK.capabilityName())) ); } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java index 17b60ed94be20..4e7f207269721 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java @@ -38,7 +38,7 @@ public class RestSampleTestCase extends ESRestTestCase { public void skipWhenSampleDisabled() throws IOException { assumeTrue( "Requires SAMPLE capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.SAMPLE_V3.capabilityName())) + RestEsqlTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.SAMPLE_V3.capabilityName())) ); } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java index c3ba8c9036eea..30af8d4045d19 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java @@ -9,12 +9,12 @@ import org.elasticsearch.xpack.esql.CsvSpecReader; import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; -import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.Mode; import java.io.IOException; import java.util.List; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.*; +import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; /** * Tests for FORK. We generate tests for FORK from existing CSV tests. @@ -30,10 +30,9 @@ public GenerativeForkRestTest( String testName, Integer lineNumber, CsvSpecReader.CsvTestCase testCase, - String instructions, - Mode mode + String instructions ) { - super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + super(fileName, groupName, testName, lineNumber, testCase, instructions); } @Override @@ -61,6 +60,6 @@ protected void shouldSkipTest(String testName) throws IOException { testCase.requiredCapabilities.contains(IMPLICIT_CASTING_DATE_AND_DATE_NANOS.capabilityName()) ); - assumeTrue("Cluster needs to support FORK", hasCapabilities(client(), List.of(FORK_V9.capabilityName()))); + assumeTrue("Cluster needs to support FORK", hasCapabilities(adminClient(), List.of(FORK_V9.capabilityName()))); } } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 9677a7e06948f..053f026cd0eda 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -70,7 +70,7 @@ public abstract class GenerativeRestTest extends ESRestTestCase { @Before public void setup() throws IOException { if (indexExists(CSV_DATASET_MAP.keySet().iterator().next()) == false) { - loadDataSetIntoEs(client(), true, supportsSourceFieldMapping()); + loadDataSetIntoEs(client(), true, supportsSourceFieldMapping(), false); } } @@ -209,7 +209,7 @@ private static List outputSchema(Map } private List availableIndices() throws IOException { - return availableDatasetsForEs(client(), true, supportsSourceFieldMapping()).stream() + return availableDatasetsForEs(true, supportsSourceFieldMapping(), false).stream() .filter(x -> x.requiresInferenceEndpoint() == false) .map(x -> x.indexName()) .toList(); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java index 8a92f02a6f2ec..c01141837fae3 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java @@ -308,7 +308,7 @@ public static void main(String[] args) throws IOException { } try (RestClient client = builder.build()) { - loadDataSetIntoEs(client, true, true, (restClient, indexName, indexMapping, indexSettings) -> { + loadDataSetIntoEs(client, true, true, false, (restClient, indexName, indexMapping, indexSettings) -> { // don't use ESRestTestCase methods here or, if you do, test running the main method before making the change StringBuilder jsonBody = new StringBuilder("{"); if (indexSettings != null && indexSettings.isEmpty() == false) { @@ -328,12 +328,10 @@ public static void main(String[] args) throws IOException { } public static Set availableDatasetsForEs( - RestClient client, boolean supportsIndexModeLookup, - boolean supportsSourceFieldMapping + boolean supportsSourceFieldMapping, + boolean inferenceEnabled ) throws IOException { - boolean inferenceEnabled = clusterHasSparseEmbeddingInferenceEndpoint(client); - Set testDataSets = new HashSet<>(); for (TestDataset dataset : CSV_DATASET_MAP.values()) { @@ -363,12 +361,17 @@ private static boolean isSourceMappingDataset(TestDataset dataset) throws IOExce return mappingNode.get("_source") != null; } - public static void loadDataSetIntoEs(RestClient client, boolean supportsIndexModeLookup, boolean supportsSourceFieldMapping) - throws IOException { + public static void loadDataSetIntoEs( + RestClient client, + boolean supportsIndexModeLookup, + boolean supportsSourceFieldMapping, + boolean inferenceEnabled + ) throws IOException { loadDataSetIntoEs( client, supportsIndexModeLookup, supportsSourceFieldMapping, + inferenceEnabled, (restClient, indexName, indexMapping, indexSettings) -> { ESRestTestCase.createIndex(restClient, indexName, indexSettings, indexMapping, null); } @@ -379,12 +382,13 @@ private static void loadDataSetIntoEs( RestClient client, boolean supportsIndexModeLookup, boolean supportsSourceFieldMapping, + boolean inferenceEnabled, IndexCreator indexCreator ) throws IOException { Logger logger = LogManager.getLogger(CsvTestsDataLoader.class); Set loadedDatasets = new HashSet<>(); - for (var dataset : availableDatasetsForEs(client, supportsIndexModeLookup, supportsSourceFieldMapping)) { + for (var dataset : availableDatasetsForEs(supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled)) { load(client, dataset, logger, indexCreator); loadedDatasets.add(dataset.indexName); }