diff --git a/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java b/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java index da7f1feeae2ea..b5cdf1a355f59 100644 --- a/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java +++ b/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java @@ -28,7 +28,6 @@ import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.test.cluster.FeatureFlag; import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider; -import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ObjectPath; import org.elasticsearch.test.rest.TestFeatureService; import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi; @@ -50,7 +49,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -315,13 +313,10 @@ protected ClientYamlTestExecutionContext createRestTestExecutionContext( // Reconcile and provide unified features, os, version(s), based on both clientYamlTestClient and searchYamlTestClient var searchOs = readOsFromNodesInfo(adminSearchClient); var searchNodeVersions = readVersionsFromNodesInfo(adminSearchClient); - var semanticNodeVersions = searchNodeVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); + final TestFeatureService searchTestFeatureService = createTestFeatureService( getClusterStateFeatures(adminSearchClient), - semanticNodeVersions + fromSemanticVersions(searchNodeVersions) ); final TestFeatureService combinedTestFeatureService = (featureId, any) -> { boolean adminFeature = testFeatureService.clusterHasFeature(featureId, any); diff --git a/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java b/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java index e7c3673dadc96..5bb66985a3ab8 100644 --- a/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java +++ b/qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java @@ -30,7 +30,6 @@ import org.elasticsearch.test.cluster.FeatureFlag; import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider; import org.elasticsearch.test.cluster.util.resource.Resource; -import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ObjectPath; import org.elasticsearch.test.rest.TestFeatureService; import org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT.TestCandidateAwareClient; @@ -45,7 +44,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -298,14 +296,9 @@ protected ClientYamlTestExecutionContext createRestTestExecutionContext( var searchOs = readOsFromNodesInfo(adminSearchClient); var searchNodeVersions = readVersionsFromNodesInfo(adminSearchClient); - var semanticNodeVersions = searchNodeVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); - final TestFeatureService searchTestFeatureService = createTestFeatureService( getClusterStateFeatures(adminSearchClient), - semanticNodeVersions + fromSemanticVersions(searchNodeVersions) ); final TestFeatureService combinedTestFeatureService = (featureId, any) -> { diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java index 5ddd925e4e803..3fb11cfe5e153 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java @@ -176,6 +176,10 @@ public abstract class ESRestTestCase extends ESTestCase { private static final Pattern SEMANTIC_VERSION_PATTERN = Pattern.compile("^(\\d+\\.\\d+\\.\\d+)\\D?.*"); + public interface VersionFeaturesPredicate { + boolean test(Version featureVersion, boolean canMatchAnyNode); + } + private static final Logger SUITE_LOGGER = LogManager.getLogger(ESRestTestCase.class); private static final String EXPECTED_ROLLUP_WARNING_MESSAGE = @@ -446,13 +450,7 @@ public void initClient() throws IOException { } } nodesVersions = Collections.unmodifiableSet(versions); - - var semanticNodeVersions = nodesVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); - assert semanticNodeVersions.isEmpty() == false || serverless; - testFeatureService = createTestFeatureService(getClusterStateFeatures(adminClient), semanticNodeVersions); + testFeatureService = createTestFeatureService(getClusterStateFeatures(adminClient), fromSemanticVersions(nodesVersions)); configureProjects(); } @@ -467,9 +465,9 @@ public void initClient() throws IOException { protected final TestFeatureService createTestFeatureService( Map> clusterStateFeatures, - Set semanticNodeVersions + VersionFeaturesPredicate versionFeaturesPredicate ) { - return new ESRestTestFeatureService(semanticNodeVersions, clusterStateFeatures.values()); + return new ESRestTestFeatureService(versionFeaturesPredicate, clusterStateFeatures.values()); } protected static boolean has(ProductFeature feature) { @@ -2584,6 +2582,27 @@ public static Optional parseLegacyVersion(String version) { return Optional.empty(); } + public static VersionFeaturesPredicate fromSemanticVersions(Set nodesVersions) { + Set semanticNodeVersions = nodesVersions.stream() + .map(ESRestTestCase::parseLegacyVersion) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + if (semanticNodeVersions.isEmpty()) { + // Nodes do not have a semantic version (e.g. serverless). + // We assume the cluster is on the "latest version", and all is supported. + return ((featureVersion, canMatchAnyNode) -> true); + } + + return (featureVersion, canMatchAnyNode) -> { + if (canMatchAnyNode) { + return semanticNodeVersions.stream().anyMatch(nodeVersion -> nodeVersion.onOrAfter(featureVersion)); + } else { + return semanticNodeVersions.isEmpty() == false + && semanticNodeVersions.stream().allMatch(nodeVersion -> nodeVersion.onOrAfter(featureVersion)); + } + }; + } + /** * Wait for the license to be applied and active. The specified admin client is used to check the license and this is done using * {@link ESTestCase#assertBusy(CheckedRunnable)} to give some time to the License to be applied on nodes. diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java index 4c23c05ddda3c..2006abdf25632 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java @@ -42,11 +42,11 @@ class ESRestTestFeatureService implements TestFeatureService { */ private static final Pattern VERSION_FEATURE_PATTERN = Pattern.compile("gte_v(\\d+\\.\\d+\\.\\d+)"); - private final Collection nodeVersions; + private final ESRestTestCase.VersionFeaturesPredicate versionFeaturesPredicate; private final Collection> nodeFeatures; - ESRestTestFeatureService(Set nodeVersions, Collection> nodeFeatures) { - this.nodeVersions = nodeVersions; + ESRestTestFeatureService(ESRestTestCase.VersionFeaturesPredicate versionFeaturesPredicate, Collection> nodeFeatures) { + this.versionFeaturesPredicate = versionFeaturesPredicate; this.nodeFeatures = nodeFeatures; } @@ -55,8 +55,8 @@ private static boolean checkCollection(Collection coll, Predicate pred } @Override - public boolean clusterHasFeature(String featureId, boolean any) { - if (checkCollection(nodeFeatures, s -> s.contains(featureId), any)) { + public boolean clusterHasFeature(String featureId, boolean canMatchAnyNode) { + if (checkCollection(nodeFeatures, s -> s.contains(featureId), canMatchAnyNode)) { return true; } if (MetadataHolder.FEATURE_NAMES.contains(featureId)) { @@ -88,7 +88,7 @@ public boolean clusterHasFeature(String featureId, boolean any) { ); } - return checkCollection(nodeVersions, v -> v.onOrAfter(extractedVersion), any); + return versionFeaturesPredicate.test(extractedVersion, canMatchAnyNode); } if (hasFeatureMetadata()) { diff --git a/test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java b/test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java index aade3c0e86cde..0948f1ae63283 100644 --- a/test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java +++ b/test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/ESClientYamlSuiteTestCase.java @@ -54,7 +54,6 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import java.util.stream.Collectors; /** * Runs a suite of yaml tests shared with all the official Elasticsearch @@ -127,13 +126,9 @@ public void initAndResetContext() throws Exception { logger.info("initializing client, node versions [{}], hosts {}, os [{}]", nodesVersions, hosts, os); - var semanticNodeVersions = nodesVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); final TestFeatureService testFeatureService = createTestFeatureService( getClusterStateFeatures(adminClient()), - semanticNodeVersions + fromSemanticVersions(nodesVersions) ); logger.info("initializing client, node versions [{}], hosts {}, os [{}]", nodesVersions, hosts, os); 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 edb579024da31..ac4a5ec4d4721 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 @@ -20,7 +20,6 @@ import org.elasticsearch.core.IOUtils; import org.elasticsearch.test.TestClustersThreadFilter; import org.elasticsearch.test.cluster.ElasticsearchCluster; -import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.TestFeatureService; import org.elasticsearch.xpack.esql.CsvSpecReader; import org.elasticsearch.xpack.esql.CsvSpecReader.CsvTestCase; @@ -38,7 +37,6 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -179,11 +177,10 @@ protected void shouldSkipTest(String testName) throws IOException { private TestFeatureService remoteFeaturesService() throws IOException { if (remoteFeaturesService == null) { var remoteNodeVersions = readVersionsFromNodesInfo(remoteClusterClient()); - var semanticNodeVersions = remoteNodeVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); - remoteFeaturesService = createTestFeatureService(getClusterStateFeatures(remoteClusterClient()), semanticNodeVersions); + remoteFeaturesService = createTestFeatureService( + getClusterStateFeatures(remoteClusterClient()), + fromSemanticVersions(remoteNodeVersions) + ); } return remoteFeaturesService; }