Replace Version with String in YAML framework, pushing Version parsing where is actually needed (#103311)

* Pushing down node versions as strings

Deferring Version parsing to the actual places where a minimum node version/common cluster version is needed; eventually this will be completely lazy and/or replaced by other checks (e.g. features).
Combine versions, oses and features in multi-cluster YAML test contexts.
This commit is contained in:
Lorenzo Dematté 2024-01-03 09:29:40 +01:00 committed by GitHub
parent 28362cd9b4
commit cea88cbeec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 263 additions and 231 deletions

View file

@ -16,7 +16,6 @@ import org.apache.http.HttpHost;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.tests.util.TimeUnits; import org.apache.lucene.tests.util.TimeUnits;
import org.elasticsearch.Version;
import org.elasticsearch.client.NodeSelector; import org.elasticsearch.client.NodeSelector;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response; import org.elasticsearch.client.Response;
@ -30,6 +29,7 @@ import org.elasticsearch.test.cluster.FeatureFlag;
import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider; import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.ObjectPath; import org.elasticsearch.test.rest.ObjectPath;
import org.elasticsearch.test.rest.TestFeatureService;
import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi; import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi;
import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestSpec; import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestSpec;
import org.elasticsearch.test.rest.yaml.section.ClientYamlTestSection; import org.elasticsearch.test.rest.yaml.section.ClientYamlTestSection;
@ -45,12 +45,15 @@ import org.junit.rules.RuleChain;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Predicate; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
@ -286,29 +289,55 @@ public class CcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
protected ClientYamlTestExecutionContext createRestTestExecutionContext( protected ClientYamlTestExecutionContext createRestTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate, ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
final Version esVersion, final Set<String> nodesVersions,
final Predicate<String> clusterFeaturesPredicate, final TestFeatureService testFeatureService,
final String os final Set<String> osSet
) { ) {
// depending on the API called, we either return the client running against the "write" or the "search" cluster here try {
// Ensure the test specific initialization is run by calling it explicitly (@Before annotations on base-derived class may
// TODO: reconcile and provide unified features, os, version(s), based on both clientYamlTestClient and searchYamlTestClient // be called in a different order)
return new ClientYamlTestExecutionContext( initSearchClient();
clientYamlTestCandidate, // Reconcile and provide unified features, os, version(s), based on both clientYamlTestClient and searchYamlTestClient
clientYamlTestClient, var searchOs = readOsFromNodesInfo(adminSearchClient);
randomizeContentType(), var searchNodeVersions = readVersionsFromNodesInfo(adminSearchClient);
esVersion, var semanticNodeVersions = searchNodeVersions.stream()
ESRestTestCase::clusterHasFeature, .map(ESRestTestCase::parseLegacyVersion)
os .flatMap(Optional::stream)
) { .collect(Collectors.toSet());
protected ClientYamlTestClient clientYamlTestClient(String apiName) { final TestFeatureService searchTestFeatureService = createTestFeatureService(
if (CCS_APIS.contains(apiName)) { getClusterStateFeatures(adminSearchClient),
return searchYamlTestClient; semanticNodeVersions
} else { );
return super.clientYamlTestClient(apiName); final TestFeatureService combinedTestFeatureService = new TestFeatureService() {
@Override
public boolean clusterHasFeature(String featureId) {
return testFeatureService.clusterHasFeature(featureId) && searchTestFeatureService.clusterHasFeature(featureId);
} }
} };
}; final Set<String> combinedOsSet = Stream.concat(osSet.stream(), Stream.of(searchOs)).collect(Collectors.toSet());
final Set<String> combinedNodeVersions = Stream.concat(nodesVersions.stream(), searchNodeVersions.stream())
.collect(Collectors.toSet());
return new ClientYamlTestExecutionContext(
clientYamlTestCandidate,
clientYamlTestClient,
randomizeContentType(),
combinedNodeVersions,
combinedTestFeatureService,
combinedOsSet
) {
// depending on the API called, we either return the client running against the "write" or the "search" cluster here
protected ClientYamlTestClient clientYamlTestClient(String apiName) {
if (CCS_APIS.contains(apiName)) {
return searchYamlTestClient;
} else {
return super.clientYamlTestClient(apiName);
}
}
};
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} }
@AfterClass @AfterClass

View file

@ -15,7 +15,6 @@ import org.apache.http.HttpHost;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.tests.util.TimeUnits; import org.apache.lucene.tests.util.TimeUnits;
import org.elasticsearch.Version;
import org.elasticsearch.client.Request; import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response; import org.elasticsearch.client.Response;
@ -32,6 +31,7 @@ import org.elasticsearch.test.cluster.local.LocalClusterConfigProvider;
import org.elasticsearch.test.cluster.util.resource.Resource; import org.elasticsearch.test.cluster.util.resource.Resource;
import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.ObjectPath; import org.elasticsearch.test.rest.ObjectPath;
import org.elasticsearch.test.rest.TestFeatureService;
import org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT.TestCandidateAwareClient; import org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT.TestCandidateAwareClient;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
@ -44,8 +44,11 @@ import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT.CCS_APIS; import static org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT.CCS_APIS;
@ -271,29 +274,55 @@ public class RcsCcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
protected ClientYamlTestExecutionContext createRestTestExecutionContext( protected ClientYamlTestExecutionContext createRestTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate, ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
final Version esVersion, final Set<String> nodesVersions,
final Predicate<String> clusterFeaturesPredicate, final TestFeatureService testFeatureService,
final String os final Set<String> osSet
) { ) {
// depending on the API called, we either return the client running against the "write" or the "search" cluster here try {
// Ensure the test specific initialization is run by calling it explicitly (@Before annotations on base-derived class may
// TODO: reconcile and provide unified features, os, version(s), based on both clientYamlTestClient and searchYamlTestClient // be called in a different order)
return new ClientYamlTestExecutionContext( initSearchClient();
clientYamlTestCandidate, // Reconcile and provide unified features, os, version(s), based on both clientYamlTestClient and searchYamlTestClient
clientYamlTestClient, var searchOs = readOsFromNodesInfo(adminSearchClient);
randomizeContentType(), var searchNodeVersions = readVersionsFromNodesInfo(adminSearchClient);
esVersion, var semanticNodeVersions = searchNodeVersions.stream()
ESRestTestCase::clusterHasFeature, .map(ESRestTestCase::parseLegacyVersion)
os .flatMap(Optional::stream)
) { .collect(Collectors.toSet());
protected ClientYamlTestClient clientYamlTestClient(String apiName) { final TestFeatureService searchTestFeatureService = createTestFeatureService(
if (CCS_APIS.contains(apiName)) { getClusterStateFeatures(adminSearchClient),
return searchYamlTestClient; semanticNodeVersions
} else { );
return super.clientYamlTestClient(apiName); final TestFeatureService combinedTestFeatureService = new TestFeatureService() {
@Override
public boolean clusterHasFeature(String featureId) {
return testFeatureService.clusterHasFeature(featureId) && searchTestFeatureService.clusterHasFeature(featureId);
} }
} };
}; final Set<String> combinedOsSet = Stream.concat(osSet.stream(), Stream.of(searchOs)).collect(Collectors.toSet());
final Set<String> combinedNodeVersions = Stream.concat(nodesVersions.stream(), searchNodeVersions.stream())
.collect(Collectors.toSet());
return new ClientYamlTestExecutionContext(
clientYamlTestCandidate,
clientYamlTestClient,
randomizeContentType(),
combinedNodeVersions,
combinedTestFeatureService,
combinedOsSet
) {
// depending on the API called, we either return the client running against the "write" or the "search" cluster here
protected ClientYamlTestClient clientYamlTestClient(String apiName) {
if (CCS_APIS.contains(apiName)) {
return searchYamlTestClient;
} else {
return super.clientYamlTestClient(apiName);
}
}
};
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} }
@AfterClass @AfterClass

View file

@ -13,26 +13,27 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
import org.apache.lucene.tests.util.TimeUnits; import org.apache.lucene.tests.util.TimeUnits;
import org.elasticsearch.Version; import org.elasticsearch.test.rest.TestFeatureService;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ClientYamlTestClient; import org.elasticsearch.test.rest.yaml.ClientYamlTestClient;
import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext; import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import java.util.function.Predicate; import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@TimeoutSuite(millis = 5 * TimeUnits.MINUTE) // to account for slow as hell VMs @TimeoutSuite(millis = 5 * TimeUnits.MINUTE) // to account for slow as hell VMs
public class MultiClusterSearchYamlTestSuiteIT extends ESClientYamlSuiteTestCase { public class MultiClusterSearchYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
private static Version remoteEsVersion = null; private static String remoteEsVersion = null;
@BeforeClass @BeforeClass
public static void determineRemoteClusterMinimumVersion() { public static void readRemoteClusterVersion() {
String remoteClusterVersion = System.getProperty("tests.rest.remote_cluster_version"); String remoteClusterVersion = System.getProperty("tests.rest.remote_cluster_version");
if (remoteClusterVersion != null) { if (remoteClusterVersion != null) {
remoteEsVersion = Version.fromString(remoteClusterVersion); remoteEsVersion = remoteClusterVersion;
} }
} }
@ -40,26 +41,32 @@ public class MultiClusterSearchYamlTestSuiteIT extends ESClientYamlSuiteTestCase
protected ClientYamlTestExecutionContext createRestTestExecutionContext( protected ClientYamlTestExecutionContext createRestTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate, ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
final Version esVersion, final Set<String> nodesVersions,
final Predicate<String> clusterFeaturesPredicate, final TestFeatureService testFeatureService,
final String os final Set<String> osSet
) { ) {
/* /*
* Since the esVersion is used to skip tests in ESClientYamlSuiteTestCase, we also take into account the * Since the esVersion is used to skip tests in ESClientYamlSuiteTestCase, we also take into account the
* remote cluster version here and return it if it is lower than the local client version. This is used to * remote cluster version here. This is used to skip tests if some feature isn't available on the remote cluster yet.
* skip tests if some feature isn't available on the remote cluster yet.
*/ */
final Version commonEsVersion = remoteEsVersion != null && remoteEsVersion.before(esVersion) ? remoteEsVersion : esVersion; final Set<String> commonVersions;
if (remoteEsVersion == null || nodesVersions.contains(remoteEsVersion)) {
// TODO: same for os and features commonVersions = nodesVersions;
} else {
var versionsCopy = new HashSet<>(nodesVersions);
versionsCopy.add(remoteEsVersion);
commonVersions = Collections.unmodifiableSet(versionsCopy);
}
// TODO: same for os and features. Better to do that once this test(s) have been migrated to the new ElasticsearchCluster-based
// framework. See CcsCommonYamlTestSuiteIT for example.
return new ClientYamlTestExecutionContext( return new ClientYamlTestExecutionContext(
clientYamlTestCandidate, clientYamlTestCandidate,
clientYamlTestClient, clientYamlTestClient,
randomizeContentType(), randomizeContentType(),
commonEsVersion, commonVersions,
ESRestTestCase::clusterHasFeature, testFeatureService,
os osSet
); );
} }

View file

@ -337,7 +337,7 @@ public abstract class ESRestTestCase extends ESTestCase {
? List.of(new RestTestLegacyFeatures(), new ESRestTestCaseHistoricalFeatures()) ? List.of(new RestTestLegacyFeatures(), new ESRestTestCaseHistoricalFeatures())
: List.of(new RestTestLegacyFeatures()); : List.of(new RestTestLegacyFeatures());
return new TestFeatureService( return new ESRestTestFeatureService(
hasHistoricalFeaturesInformation, hasHistoricalFeaturesInformation,
providers, providers,
semanticNodeVersions, semanticNodeVersions,

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.test.rest;
import org.elasticsearch.Version;
import org.elasticsearch.core.Strings;
import org.elasticsearch.features.FeatureData;
import org.elasticsearch.features.FeatureSpecification;
import java.util.Collection;
import java.util.List;
import java.util.NavigableMap;
import java.util.Set;
import java.util.function.Predicate;
class ESRestTestFeatureService implements TestFeatureService {
private final Predicate<String> historicalFeaturesPredicate;
private final Set<String> clusterStateFeatures;
ESRestTestFeatureService(
boolean hasHistoricalFeaturesInformation,
List<? extends FeatureSpecification> specs,
Collection<Version> nodeVersions,
Set<String> clusterStateFeatures
) {
var minNodeVersion = nodeVersions.stream().min(Version::compareTo);
var featureData = FeatureData.createFromSpecifications(specs);
var historicalFeatures = featureData.getHistoricalFeatures();
var allHistoricalFeatures = historicalFeatures.lastEntry() == null ? Set.of() : historicalFeatures.lastEntry().getValue();
var errorMessage = hasHistoricalFeaturesInformation
? "Check the feature has been added to the correct FeatureSpecification in the relevant module or, if this is a "
+ "legacy feature used only in tests, to a test-only FeatureSpecification"
: "This test is running on the legacy test framework; historical features from production code will not be available."
+ " You need to port the test to the new test plugins in order to use historical features from production code."
+ " If this is a legacy feature used only in tests, you can add it to a test-only FeatureSpecification";
this.historicalFeaturesPredicate = minNodeVersion.<Predicate<String>>map(v -> featureId -> {
assert allHistoricalFeatures.contains(featureId) : Strings.format("Unknown historical feature %s: %s", featureId, errorMessage);
return hasHistoricalFeature(historicalFeatures, v, featureId);
}).orElse(featureId -> {
// We can safely assume that new non-semantic versions (serverless) support all historical features
assert allHistoricalFeatures.contains(featureId) : Strings.format("Unknown historical feature %s: %s", featureId, errorMessage);
return true;
});
this.clusterStateFeatures = clusterStateFeatures;
}
private static boolean hasHistoricalFeature(NavigableMap<Version, Set<String>> historicalFeatures, Version version, String featureId) {
var features = historicalFeatures.floorEntry(version);
return features != null && features.getValue().contains(featureId);
}
public boolean clusterHasFeature(String featureId) {
if (clusterStateFeatures.contains(featureId)) {
return true;
}
return historicalFeaturesPredicate.test(featureId);
}
}

View file

@ -8,58 +8,6 @@
package org.elasticsearch.test.rest; package org.elasticsearch.test.rest;
import org.elasticsearch.Version; public interface TestFeatureService {
import org.elasticsearch.core.Strings; boolean clusterHasFeature(String featureId);
import org.elasticsearch.features.FeatureData;
import org.elasticsearch.features.FeatureSpecification;
import java.util.Collection;
import java.util.List;
import java.util.NavigableMap;
import java.util.Set;
import java.util.function.Predicate;
public class TestFeatureService {
private final Predicate<String> historicalFeaturesPredicate;
private final Set<String> clusterStateFeatures;
TestFeatureService(
boolean hasHistoricalFeaturesInformation,
List<? extends FeatureSpecification> specs,
Collection<Version> nodeVersions,
Set<String> clusterStateFeatures
) {
var minNodeVersion = nodeVersions.stream().min(Version::compareTo);
var featureData = FeatureData.createFromSpecifications(specs);
var historicalFeatures = featureData.getHistoricalFeatures();
var allHistoricalFeatures = historicalFeatures.lastEntry() == null ? Set.of() : historicalFeatures.lastEntry().getValue();
var errorMessage = hasHistoricalFeaturesInformation
? "Check the feature has been added to the correct FeatureSpecification in the relevant module or, if this is a "
+ "legacy feature used only in tests, to a test-only FeatureSpecification"
: "This test is running on the legacy test framework; historical features from production code will not be available."
+ " You need to port the test to the new test plugins in order to use historical features from production code."
+ " If this is a legacy feature used only in tests, you can add it to a test-only FeatureSpecification";
this.historicalFeaturesPredicate = minNodeVersion.<Predicate<String>>map(v -> featureId -> {
assert allHistoricalFeatures.contains(featureId) : Strings.format("Unknown historical feature %s: %s", featureId, errorMessage);
return hasHistoricalFeature(historicalFeatures, v, featureId);
}).orElse(featureId -> {
// We can safely assume that new non-semantic versions (serverless) support all historical features
assert allHistoricalFeatures.contains(featureId) : Strings.format("Unknown historical feature %s: %s", featureId, errorMessage);
return true;
});
this.clusterStateFeatures = clusterStateFeatures;
}
private static boolean hasHistoricalFeature(NavigableMap<Version, Set<String>> historicalFeatures, Version version, String featureId) {
var features = historicalFeatures.floorEntry(version);
return features != null && features.getValue().contains(featureId);
}
public boolean clusterHasFeature(String featureId) {
if (clusterStateFeatures.contains(featureId)) {
return true;
}
return historicalFeaturesPredicate.test(featureId);
}
} }

View file

@ -15,11 +15,8 @@ import org.apache.http.entity.ContentType;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.client.NodeSelector; import org.elasticsearch.client.NodeSelector;
import org.elasticsearch.common.VersionId;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.Stash; import org.elasticsearch.test.rest.Stash;
import org.elasticsearch.test.rest.TestFeatureService; import org.elasticsearch.test.rest.TestFeatureService;
import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi; import org.elasticsearch.test.rest.yaml.restspec.ClientYamlSuiteRestApi;
@ -33,10 +30,8 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Predicate;
/** /**
* Execution context passed across the REST tests. * Execution context passed across the REST tests.
@ -56,43 +51,21 @@ public class ClientYamlTestExecutionContext {
private ClientYamlTestResponse response; private ClientYamlTestResponse response;
private final Version esVersion; private final Set<String> nodesVersions;
private final String os; private final Set<String> osSet;
private final Predicate<String> clusterFeaturesPredicate; private final TestFeatureService testFeatureService;
private final boolean randomizeContentType; private final boolean randomizeContentType;
private final BiPredicate<ClientYamlSuiteRestApi, ClientYamlSuiteRestApi.Path> pathPredicate; private final BiPredicate<ClientYamlSuiteRestApi, ClientYamlSuiteRestApi.Path> pathPredicate;
// TODO: this will become the ctor once work is done on serverless side and https://github.com/elastic/elasticsearch/pull/103311/
// has been merged
public ClientYamlTestExecutionContext( public ClientYamlTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate, ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
boolean randomizeContentType, boolean randomizeContentType,
final Set<String> nodesVersions, final Set<String> nodesVersions,
final TestFeatureService testFeatureService, final TestFeatureService testFeatureService,
final Set<String> osList, final Set<String> osSet
BiPredicate<ClientYamlSuiteRestApi, ClientYamlSuiteRestApi.Path> pathPredicate
) {
this(
clientYamlTestCandidate,
clientYamlTestClient,
randomizeContentType,
getEsVersion(nodesVersions),
testFeatureService::clusterHasFeature,
osList.iterator().next(),
pathPredicate
);
}
public ClientYamlTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient,
boolean randomizeContentType,
final Set<String> nodesVersions,
final TestFeatureService testFeatureService,
final Set<String> osList
) { ) {
this( this(
clientYamlTestCandidate, clientYamlTestCandidate,
@ -100,56 +73,26 @@ public class ClientYamlTestExecutionContext {
randomizeContentType, randomizeContentType,
nodesVersions, nodesVersions,
testFeatureService, testFeatureService,
osList, osSet,
(ignoreApi, ignorePath) -> true (ignoreApi, ignorePath) -> true
); );
} }
@Deprecated
static Version getEsVersion(Set<String> nodesVersions) {
return nodesVersions.stream()
.map(ESRestTestCase::parseLegacyVersion)
.flatMap(Optional::stream)
.min(VersionId::compareTo)
.orElse(Version.CURRENT);
}
@Deprecated
public ClientYamlTestExecutionContext( public ClientYamlTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate, ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
boolean randomizeContentType, boolean randomizeContentType,
final Version esVersion, final Set<String> nodesVersions,
final Predicate<String> clusterFeaturesPredicate, final TestFeatureService testFeatureService,
final String os final Set<String> osSet,
) {
this(
clientYamlTestCandidate,
clientYamlTestClient,
randomizeContentType,
esVersion,
clusterFeaturesPredicate,
os,
(ignoreApi, ignorePath) -> true
);
}
@Deprecated
public ClientYamlTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient,
boolean randomizeContentType,
final Version esVersion,
final Predicate<String> clusterFeaturesPredicate,
final String os,
BiPredicate<ClientYamlSuiteRestApi, ClientYamlSuiteRestApi.Path> pathPredicate BiPredicate<ClientYamlSuiteRestApi, ClientYamlSuiteRestApi.Path> pathPredicate
) { ) {
this.clientYamlTestClient = clientYamlTestClient; this.clientYamlTestClient = clientYamlTestClient;
this.clientYamlTestCandidate = clientYamlTestCandidate; this.clientYamlTestCandidate = clientYamlTestCandidate;
this.randomizeContentType = randomizeContentType; this.randomizeContentType = randomizeContentType;
this.esVersion = esVersion; this.nodesVersions = nodesVersions;
this.clusterFeaturesPredicate = clusterFeaturesPredicate; this.testFeatureService = testFeatureService;
this.os = os; this.osSet = osSet;
this.pathPredicate = pathPredicate; this.pathPredicate = pathPredicate;
} }
@ -304,14 +247,14 @@ public class ClientYamlTestExecutionContext {
} }
/** /**
* @return the version of the oldest node in the cluster * @return the distinct node versions running in the cluster
*/ */
public Version esVersion() { public Set<String> nodesVersions() {
return esVersion; return nodesVersions;
} }
public String os() { public String os() {
return os; return osSet.iterator().next();
} }
public ClientYamlTestCandidate getClientYamlTestCandidate() { public ClientYamlTestCandidate getClientYamlTestCandidate() {
@ -319,6 +262,6 @@ public class ClientYamlTestExecutionContext {
} }
public boolean clusterHasFeature(String featureId) { public boolean clusterHasFeature(String featureId) {
return clusterFeaturesPredicate.test(featureId); return testFeatureService.clusterHasFeature(featureId);
} }
} }

View file

@ -26,6 +26,7 @@ import org.elasticsearch.client.WarningsHandler;
import org.elasticsearch.client.sniff.ElasticsearchNodesSniffer; import org.elasticsearch.client.sniff.ElasticsearchNodesSniffer;
import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.VersionId;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.IOUtils;
@ -61,10 +62,8 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext.getEsVersion;
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
/** /**
@ -144,6 +143,8 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
final Set<String> nodesVersions = getCachedNodesVersions(); final Set<String> nodesVersions = getCachedNodesVersions();
final String os = readOsFromNodesInfo(adminClient()); final String os = readOsFromNodesInfo(adminClient());
logger.info("initializing client, node versions [{}], hosts {}, os [{}]", nodesVersions, hosts, os);
var semanticNodeVersions = nodesVersions.stream() var semanticNodeVersions = nodesVersions.stream()
.map(ESRestTestCase::parseLegacyVersion) .map(ESRestTestCase::parseLegacyVersion)
.flatMap(Optional::stream) .flatMap(Optional::stream)
@ -199,32 +200,15 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
ClientYamlTestClient clientYamlTestClient, ClientYamlTestClient clientYamlTestClient,
final Set<String> nodesVersions, final Set<String> nodesVersions,
final TestFeatureService testFeatureService, final TestFeatureService testFeatureService,
final Set<String> osList final Set<String> osSet
) {
return createRestTestExecutionContext(
clientYamlTestCandidate,
clientYamlTestClient,
getEsVersion(nodesVersions),
testFeatureService::clusterHasFeature,
osList.iterator().next()
);
}
@Deprecated
protected ClientYamlTestExecutionContext createRestTestExecutionContext(
ClientYamlTestCandidate clientYamlTestCandidate,
ClientYamlTestClient clientYamlTestClient,
final Version esVersion,
final Predicate<String> clusterFeaturesPredicate,
final String os
) { ) {
return new ClientYamlTestExecutionContext( return new ClientYamlTestExecutionContext(
clientYamlTestCandidate, clientYamlTestCandidate,
clientYamlTestClient, clientYamlTestClient,
randomizeContentType(), randomizeContentType(),
esVersion, nodesVersions,
clusterFeaturesPredicate, testFeatureService,
os osSet
); );
} }
@ -463,20 +447,31 @@ public abstract class ESClientYamlSuiteTestCase extends ESRestTestCase {
); );
} }
// Try to extract the minimum node version. Assume CURRENT if nodes have non-semantic versions
// TODO: after https://github.com/elastic/elasticsearch/pull/103404 is merged, we can push this logic into SkipVersionContext.
// This way will have version parsing only when we actually have to skip on a version, we can remove the default and throw an
// IllegalArgumentException instead (attempting to skip on version where version is not semantic)
var oldestNodeVersion = restTestExecutionContext.nodesVersions()
.stream()
.map(ESRestTestCase::parseLegacyVersion)
.flatMap(Optional::stream)
.min(VersionId::compareTo)
.orElse(Version.CURRENT);
// skip test if the whole suite (yaml file) is disabled // skip test if the whole suite (yaml file) is disabled
assumeFalse( assumeFalse(
testCandidate.getSetupSection().getSkipSection().getSkipMessage(testCandidate.getSuitePath()), testCandidate.getSetupSection().getSkipSection().getSkipMessage(testCandidate.getSuitePath()),
testCandidate.getSetupSection().getSkipSection().skip(restTestExecutionContext.esVersion()) testCandidate.getSetupSection().getSkipSection().skip(oldestNodeVersion)
); );
// skip test if the whole suite (yaml file) is disabled // skip test if the whole suite (yaml file) is disabled
assumeFalse( assumeFalse(
testCandidate.getTeardownSection().getSkipSection().getSkipMessage(testCandidate.getSuitePath()), testCandidate.getTeardownSection().getSkipSection().getSkipMessage(testCandidate.getSuitePath()),
testCandidate.getTeardownSection().getSkipSection().skip(restTestExecutionContext.esVersion()) testCandidate.getTeardownSection().getSkipSection().skip(oldestNodeVersion)
); );
// skip test if test section is disabled // skip test if test section is disabled
assumeFalse( assumeFalse(
testCandidate.getTestSection().getSkipSection().getSkipMessage(testCandidate.getTestPath()), testCandidate.getTestSection().getSkipSection().getSkipMessage(testCandidate.getTestPath()),
testCandidate.getTestSection().getSkipSection().skip(restTestExecutionContext.esVersion()) testCandidate.getTestSection().getSkipSection().skip(oldestNodeVersion)
); );
// skip test if os is excluded // skip test if os is excluded
assumeFalse( assumeFalse(

View file

@ -17,10 +17,12 @@ import org.elasticsearch.client.Node;
import org.elasticsearch.client.NodeSelector; import org.elasticsearch.client.NodeSelector;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.VersionId;
import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.logging.HeaderWarning;
import org.elasticsearch.core.Tuple; import org.elasticsearch.core.Tuple;
import org.elasticsearch.core.UpdateForV9; import org.elasticsearch.core.UpdateForV9;
import org.elasticsearch.rest.action.admin.indices.RestPutIndexTemplateAction; import org.elasticsearch.rest.action.admin.indices.RestPutIndexTemplateAction;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.RestTestLegacyFeatures; import org.elasticsearch.test.rest.RestTestLegacyFeatures;
import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext; import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext;
import org.elasticsearch.test.rest.yaml.ClientYamlTestResponse; import org.elasticsearch.test.rest.yaml.ClientYamlTestResponse;
@ -39,6 +41,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -376,8 +379,14 @@ public class DoSection implements ExecutableSection {
// This is really difficult to express just with features, so I will break it down into 2 parts: version check for v7, // This is really difficult to express just with features, so I will break it down into 2 parts: version check for v7,
// and feature check for v8. This way the version check can be removed once we move to v9 // and feature check for v8. This way the version check can be removed once we move to v9
@UpdateForV9 @UpdateForV9
var fixedInV7 = executionContext.esVersion().major == Version.V_7_17_0.major var fixedInV7 = executionContext.nodesVersions()
&& executionContext.esVersion().onOrAfter(Version.V_7_17_2); .stream()
.map(ESRestTestCase::parseLegacyVersion)
.flatMap(Optional::stream)
.min(VersionId::compareTo)
.map(v -> v.major == Version.V_7_17_0.major && v.onOrAfter(Version.V_7_17_2))
.orElse(false);
var fixedProductionHeader = fixedInV7 var fixedProductionHeader = fixedInV7
|| executionContext.clusterHasFeature(RestTestLegacyFeatures.REST_ELASTIC_PRODUCT_HEADER_PRESENT.id()); || executionContext.clusterHasFeature(RestTestLegacyFeatures.REST_ELASTIC_PRODUCT_HEADER_PRESENT.id());
if (fixedProductionHeader) { if (fixedProductionHeader) {

View file

@ -9,31 +9,38 @@
package org.elasticsearch.test.rest.yaml; package org.elasticsearch.test.rest.yaml;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.elasticsearch.Version;
import org.elasticsearch.client.NodeSelector; import org.elasticsearch.client.NodeSelector;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils; import org.elasticsearch.test.rest.TestFeatureService;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
public class ClientYamlTestExecutionContextTests extends ESTestCase { public class ClientYamlTestExecutionContextTests extends ESTestCase {
private static class MockTestFeatureService implements TestFeatureService {
@Override
public boolean clusterHasFeature(String featureId) {
return true;
}
}
public void testHeadersSupportStashedValueReplacement() throws IOException { public void testHeadersSupportStashedValueReplacement() throws IOException {
final AtomicReference<Map<String, String>> headersRef = new AtomicReference<>(); final AtomicReference<Map<String, String>> headersRef = new AtomicReference<>();
final Version version = VersionUtils.randomVersion(random()); final String version = randomAlphaOfLength(10);
final ClientYamlTestExecutionContext context = new ClientYamlTestExecutionContext( final ClientYamlTestExecutionContext context = new ClientYamlTestExecutionContext(
null, null,
null, null,
randomBoolean(), randomBoolean(),
version, Set.of(version),
feature -> true, new MockTestFeatureService(),
"os" Set.of("os")
) { ) {
@Override @Override
ClientYamlTestResponse callApiInternal( ClientYamlTestResponse callApiInternal(
@ -64,14 +71,14 @@ public class ClientYamlTestExecutionContextTests extends ESTestCase {
} }
public void testStashHeadersOnException() throws IOException { public void testStashHeadersOnException() throws IOException {
final Version version = VersionUtils.randomVersion(random()); final String version = randomAlphaOfLength(10);
final ClientYamlTestExecutionContext context = new ClientYamlTestExecutionContext( final ClientYamlTestExecutionContext context = new ClientYamlTestExecutionContext(
null, null,
null, null,
randomBoolean(), randomBoolean(),
version, Set.of(version),
feature -> true, new MockTestFeatureService(),
"os" Set.of("os")
) { ) {
@Override @Override
ClientYamlTestResponse callApiInternal( ClientYamlTestResponse callApiInternal(

View file

@ -16,7 +16,6 @@ import org.elasticsearch.client.NodeSelector;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.logging.HeaderWarning;
import org.elasticsearch.core.Strings; import org.elasticsearch.core.Strings;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext; import org.elasticsearch.test.rest.yaml.ClientYamlTestExecutionContext;
import org.elasticsearch.test.rest.yaml.ClientYamlTestResponse; import org.elasticsearch.test.rest.yaml.ClientYamlTestResponse;
import org.elasticsearch.xcontent.XContentLocation; import org.elasticsearch.xcontent.XContentLocation;
@ -31,6 +30,7 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
@ -610,7 +610,7 @@ public class DoSectionTests extends AbstractClientYamlTestFragmentParserTestCase
doSection.getApiCallSection().getNodeSelector() doSection.getApiCallSection().getNodeSelector()
) )
).thenReturn(mockResponse); ).thenReturn(mockResponse);
when(context.esVersion()).thenReturn(VersionUtils.randomVersion(random())); when(context.nodesVersions()).thenReturn(Set.of(randomAlphaOfLength(10)));
when(mockResponse.getHeaders("X-elastic-product")).thenReturn(List.of("Elasticsearch")); when(mockResponse.getHeaders("X-elastic-product")).thenReturn(List.of("Elasticsearch"));
doSection.execute(context); doSection.execute(context);
verify(context).callApi( verify(context).callApi(