Browse Source

Add checks for old cluster features in rolling upgrade tests (#104279)

Add the ability to test for the original/old cluster features during a rolling upgrade
* Moving ALL_FEATURES to ESRestTestCase (and make it private - only usage)
Lorenzo Dematté 1 year ago
parent
commit
be2f61a81c
14 changed files with 139 additions and 42 deletions
  1. 9 0
      qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java
  2. 9 0
      qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java
  3. 5 5
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ClusterFeatureMigrationIT.java
  4. 4 6
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DesiredNodesUpgradeIT.java
  5. 3 2
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/IndexingIT.java
  6. 20 0
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java
  7. 5 8
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java
  8. 5 7
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/TsdbIT.java
  9. 9 9
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/UpgradeWithOldIndexSettingsIT.java
  10. 16 2
      test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java
  11. 14 0
      test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java
  12. 32 2
      test/framework/src/main/java/org/elasticsearch/test/rest/RestTestLegacyFeatures.java
  13. 3 1
      test/framework/src/main/java/org/elasticsearch/test/rest/TestFeatureService.java
  14. 5 0
      test/yaml-rest-runner/src/test/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContextTests.java

+ 9 - 0
qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/CcsCommonYamlTestSuiteIT.java

@@ -23,6 +23,7 @@ import org.elasticsearch.client.RestClient;
 import org.elasticsearch.client.RestClientBuilder;
 import org.elasticsearch.common.CheckedSupplier;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.core.IOUtils;
 import org.elasticsearch.test.cluster.ElasticsearchCluster;
 import org.elasticsearch.test.cluster.FeatureFlag;
@@ -313,6 +314,14 @@ public class CcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
                 public boolean clusterHasFeature(String featureId) {
                     return testFeatureService.clusterHasFeature(featureId) && searchTestFeatureService.clusterHasFeature(featureId);
                 }
+
+                @Override
+                public Set<String> getAllSupportedFeatures() {
+                    return Sets.intersection(
+                        testFeatureService.getAllSupportedFeatures(),
+                        searchTestFeatureService.getAllSupportedFeatures()
+                    );
+                }
             };
             final Set<String> combinedOsSet = Stream.concat(osSet.stream(), Stream.of(searchOs)).collect(Collectors.toSet());
             final Set<String> combinedNodeVersions = Stream.concat(nodesVersions.stream(), searchNodeVersions.stream())

+ 9 - 0
qa/ccs-common-rest/src/yamlRestTest/java/org/elasticsearch/test/rest/yaml/RcsCcsCommonYamlTestSuiteIT.java

@@ -24,6 +24,7 @@ import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.concurrent.ThreadContext;
+import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.core.IOUtils;
 import org.elasticsearch.test.cluster.ElasticsearchCluster;
 import org.elasticsearch.test.cluster.FeatureFlag;
@@ -298,6 +299,14 @@ public class RcsCcsCommonYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
                 public boolean clusterHasFeature(String featureId) {
                     return testFeatureService.clusterHasFeature(featureId) && searchTestFeatureService.clusterHasFeature(featureId);
                 }
+
+                @Override
+                public Set<String> getAllSupportedFeatures() {
+                    return Sets.intersection(
+                        testFeatureService.getAllSupportedFeatures(),
+                        searchTestFeatureService.getAllSupportedFeatures()
+                    );
+                }
             };
             final Set<String> combinedOsSet = Stream.concat(osSet.stream(), Stream.of(searchOs)).collect(Collectors.toSet());
             final Set<String> combinedNodeVersions = Stream.concat(nodesVersions.stream(), searchNodeVersions.stream())

+ 5 - 5
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ClusterFeatureMigrationIT.java

@@ -13,7 +13,7 @@ import com.carrotsearch.randomizedtesting.annotations.Name;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.features.FeatureService;
-import org.junit.BeforeClass;
+import org.junit.Before;
 
 import java.io.IOException;
 import java.util.List;
@@ -26,11 +26,11 @@ import static org.hamcrest.Matchers.hasSize;
 
 public class ClusterFeatureMigrationIT extends ParameterizedRollingUpgradeTestCase {
 
-    @BeforeClass
-    public static void checkMigrationVersion() {
-        assumeTrue(
+    @Before
+    public void checkMigrationVersion() {
+        assumeFalse(
             "This checks migrations from before cluster features were introduced",
-            getOldClusterVersion().before(FeatureService.CLUSTER_FEATURES_ADDED_VERSION)
+            oldClusterHasFeature(FeatureService.FEATURES_SUPPORTED)
         );
     }
 

+ 4 - 6
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DesiredNodesUpgradeIT.java

@@ -16,11 +16,11 @@ import org.elasticsearch.client.Request;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.cluster.metadata.DesiredNode;
 import org.elasticsearch.cluster.metadata.DesiredNodeWithStatus;
-import org.elasticsearch.cluster.metadata.MetadataFeatures;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.test.rest.RestTestLegacyFeatures;
 import org.elasticsearch.xcontent.json.JsonXContent;
 
 import java.io.IOException;
@@ -48,13 +48,11 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
     }
 
     public void testUpgradeDesiredNodes() throws Exception {
-        assumeTrue("Desired nodes was introduced in 8.1", getOldClusterVersion().onOrAfter(Version.V_8_1_0));
+        assumeTrue("Desired nodes was introduced in 8.1", oldClusterHasFeature(RestTestLegacyFeatures.DESIRED_NODE_API_SUPPORTED));
 
-        var featureVersions = new MetadataFeatures().getHistoricalFeatures();
-
-        if (getOldClusterVersion().onOrAfter(featureVersions.get(DesiredNode.DOUBLE_PROCESSORS_SUPPORTED))) {
+        if (oldClusterHasFeature(DesiredNode.DOUBLE_PROCESSORS_SUPPORTED)) {
             assertUpgradedNodesCanReadDesiredNodes();
-        } else if (getOldClusterVersion().onOrAfter(featureVersions.get(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORTED))) {
+        } else if (oldClusterHasFeature(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORTED)) {
             assertDesiredNodesUpdatedWithRoundedUpFloatsAreIdempotent();
         } else {
             assertDesiredNodesWithFloatProcessorsAreRejectedInOlderVersions();

+ 3 - 2
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/IndexingIT.java

@@ -19,6 +19,7 @@ import org.elasticsearch.common.time.DateUtils;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.index.mapper.DateFieldMapper;
 import org.elasticsearch.test.ListMatcher;
+import org.elasticsearch.test.rest.RestTestLegacyFeatures;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentType;
 import org.elasticsearch.xcontent.json.JsonXContent;
@@ -229,7 +230,7 @@ public class IndexingIT extends ParameterizedRollingUpgradeTestCase {
     }
 
     public void testTsdb() throws IOException {
-        assumeTrue("indexing time series indices changed in 8.2.0", getOldClusterVersion().onOrAfter(Version.V_8_2_0));
+        assumeTrue("indexing time series indices changed in 8.2.0", oldClusterHasFeature(RestTestLegacyFeatures.TSDB_NEW_INDEX_FORMAT));
 
         StringBuilder bulk = new StringBuilder();
         if (isOldCluster()) {
@@ -337,7 +338,7 @@ public class IndexingIT extends ParameterizedRollingUpgradeTestCase {
     }
 
     public void testSyntheticSource() throws IOException {
-        assumeTrue("added in 8.4.0", getOldClusterVersion().onOrAfter(Version.V_8_4_0));
+        assumeTrue("added in 8.4.0", oldClusterHasFeature(RestTestLegacyFeatures.SYNTHETIC_SOURCE_SUPPORTED));
 
         if (isOldCluster()) {
             Request createIndex = new Request("PUT", "/synthetic");

+ 20 - 0
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java

@@ -15,6 +15,7 @@ import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.index.IndexVersion;
 import org.elasticsearch.test.cluster.ElasticsearchCluster;
 import org.elasticsearch.test.cluster.FeatureFlag;
@@ -69,6 +70,7 @@ public abstract class ParameterizedRollingUpgradeTestCase extends ESRestTestCase
     }
 
     private static final Set<Integer> upgradedNodes = new HashSet<>();
+    private static final Set<String> oldClusterFeatures = new HashSet<>();
     private static boolean upgradeFailed = false;
     private static IndexVersion oldIndexVersion;
 
@@ -78,6 +80,13 @@ public abstract class ParameterizedRollingUpgradeTestCase extends ESRestTestCase
         this.requestedUpgradedNodes = upgradedNodes;
     }
 
+    @Before
+    public void extractOldClusterFeatures() {
+        if (isOldCluster() && oldClusterFeatures.isEmpty()) {
+            oldClusterFeatures.addAll(testFeatureService.getAllSupportedFeatures());
+        }
+    }
+
     @Before
     public void extractOldIndexVersion() throws Exception {
         if (oldIndexVersion == null && upgradedNodes.isEmpty()) {
@@ -138,13 +147,24 @@ public abstract class ParameterizedRollingUpgradeTestCase extends ESRestTestCase
     public static void resetNodes() {
         oldIndexVersion = null;
         upgradedNodes.clear();
+        oldClusterFeatures.clear();
         upgradeFailed = false;
     }
 
+    @Deprecated // Use the new testing framework and oldClusterHasFeature(feature) instead
     protected static org.elasticsearch.Version getOldClusterVersion() {
         return org.elasticsearch.Version.fromString(OLD_CLUSTER_VERSION);
     }
 
+    protected static boolean oldClusterHasFeature(String featureId) {
+        assert oldClusterFeatures.isEmpty() == false;
+        return oldClusterFeatures.contains(featureId);
+    }
+
+    protected static boolean oldClusterHasFeature(NodeFeature feature) {
+        return oldClusterHasFeature(feature.id());
+    }
+
     protected static IndexVersion getOldClusterIndexVersion() {
         assert oldIndexVersion != null;
         return oldIndexVersion;

+ 5 - 8
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java

@@ -13,7 +13,6 @@ import com.carrotsearch.randomizedtesting.annotations.Name;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.util.EntityUtils;
-import org.elasticsearch.Version;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
@@ -24,6 +23,7 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.repositories.blobstore.BlobStoreRepository;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.test.rest.RestTestLegacyFeatures;
 import org.elasticsearch.xcontent.XContentBuilder;
 
 import java.io.IOException;
@@ -49,13 +49,10 @@ public class SnapshotBasedRecoveryIT extends ParameterizedRollingUpgradeTestCase
     }
 
     public void testSnapshotBasedRecovery() throws Exception {
-
-        assumeFalse(
-            "Cancel shard allocation command is broken for initial desired balance versions and might allocate shard "
-                + "on the node where it is not supposed to be. Fixed by https://github.com/elastic/elasticsearch/pull/93635",
-            getOldClusterVersion() == Version.V_8_6_0
-                || getOldClusterVersion() == Version.V_8_6_1
-                || getOldClusterVersion() == Version.V_8_7_0
+        assumeTrue(
+            "Cancel shard allocation command is broken for initial versions of the desired_balance allocator",
+            oldClusterHasFeature(RestTestLegacyFeatures.DESIRED_BALANCED_ALLOCATOR_SUPPORTED) == false
+                || oldClusterHasFeature(RestTestLegacyFeatures.DESIRED_BALANCED_ALLOCATOR_FIXED)
         );
 
         final String indexName = "snapshot_based_recovery";

+ 5 - 7
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/TsdbIT.java

@@ -10,11 +10,11 @@ package org.elasticsearch.upgrades;
 
 import com.carrotsearch.randomizedtesting.annotations.Name;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.common.time.DateFormatter;
 import org.elasticsearch.common.time.FormatNames;
 import org.elasticsearch.test.rest.ObjectPath;
+import org.elasticsearch.test.rest.RestTestLegacyFeatures;
 
 import java.io.IOException;
 import java.time.Instant;
@@ -130,10 +130,7 @@ public class TsdbIT extends ParameterizedRollingUpgradeTestCase {
         """;
 
     public void testTsdbDataStream() throws Exception {
-        assumeTrue(
-            "Skipping version [" + getOldClusterVersion() + "], because TSDB was GA-ed in 8.7.0",
-            getOldClusterVersion().onOrAfter(Version.V_8_7_0)
-        );
+        assumeTrue("TSDB was GA-ed in 8.7.0", oldClusterHasFeature(RestTestLegacyFeatures.TSDB_GENERALLY_AVAILABLE));
         String dataStreamName = "k8s";
         if (isOldCluster()) {
             final String INDEX_TEMPLATE = """
@@ -159,8 +156,9 @@ public class TsdbIT extends ParameterizedRollingUpgradeTestCase {
 
     public void testTsdbDataStreamWithComponentTemplate() throws Exception {
         assumeTrue(
-            "Skipping version [" + getOldClusterVersion() + "], because TSDB was GA-ed in 8.7.0 and bug was fixed in 8.11.0",
-            getOldClusterVersion().onOrAfter(Version.V_8_7_0) && getOldClusterVersion().before(Version.V_8_11_0)
+            "TSDB was GA-ed in 8.7.0 and bug was fixed in 8.11.0",
+            oldClusterHasFeature(RestTestLegacyFeatures.TSDB_GENERALLY_AVAILABLE)
+                && (oldClusterHasFeature(RestTestLegacyFeatures.TSDB_EMPTY_TEMPLATE_FIXED) == false)
         );
         String dataStreamName = "template-with-component-template";
         if (isOldCluster()) {

+ 9 - 9
qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/UpgradeWithOldIndexSettingsIT.java

@@ -10,13 +10,13 @@ package org.elasticsearch.upgrades;
 
 import com.carrotsearch.randomizedtesting.annotations.Name;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
 import org.elasticsearch.core.Strings;
+import org.elasticsearch.test.rest.RestTestLegacyFeatures;
 
 import java.io.IOException;
 import java.util.Map;
@@ -42,10 +42,7 @@ public class UpgradeWithOldIndexSettingsIT extends ParameterizedRollingUpgradeTe
             Request createTestIndex = new Request("PUT", "/" + INDEX_NAME);
             createTestIndex.setJsonEntity("{\"settings\": {\"index.indexing.slowlog.level\": \"WARN\"}}");
             createTestIndex.setOptions(expectWarnings(EXPECTED_WARNING));
-            if (getOldClusterVersion().before(Version.V_8_0_0)) {
-                // create index with settings no longer valid in 8.0
-                client().performRequest(createTestIndex);
-            } else {
+            if (oldClusterHasFeature(RestTestLegacyFeatures.INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED)) {
                 assertTrue(
                     expectThrows(ResponseException.class, () -> client().performRequest(createTestIndex)).getMessage()
                         .contains("unknown setting [index.indexing.slowlog.level]")
@@ -53,12 +50,15 @@ public class UpgradeWithOldIndexSettingsIT extends ParameterizedRollingUpgradeTe
 
                 Request createTestIndex1 = new Request("PUT", "/" + INDEX_NAME);
                 client().performRequest(createTestIndex1);
+            } else {
+                // create index with settings no longer valid in 8.0
+                client().performRequest(createTestIndex);
             }
 
             // add some data
             Request bulk = new Request("POST", "/_bulk");
             bulk.addParameter("refresh", "true");
-            if (getOldClusterVersion().before(Version.V_8_0_0)) {
+            if (oldClusterHasFeature(RestTestLegacyFeatures.INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED) == false) {
                 bulk.setOptions(expectWarnings(EXPECTED_WARNING));
             }
             bulk.setJsonEntity(Strings.format("""
@@ -70,7 +70,7 @@ public class UpgradeWithOldIndexSettingsIT extends ParameterizedRollingUpgradeTe
             // add some more data
             Request bulk = new Request("POST", "/_bulk");
             bulk.addParameter("refresh", "true");
-            if (getOldClusterVersion().before(Version.V_8_0_0)) {
+            if (oldClusterHasFeature(RestTestLegacyFeatures.INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED) == false) {
                 bulk.setOptions(expectWarnings(EXPECTED_WARNING));
             }
             bulk.setJsonEntity(Strings.format("""
@@ -79,7 +79,7 @@ public class UpgradeWithOldIndexSettingsIT extends ParameterizedRollingUpgradeTe
                 """, INDEX_NAME));
             client().performRequest(bulk);
         } else {
-            if (getOldClusterVersion().before(Version.V_8_0_0)) {
+            if (oldClusterHasFeature(RestTestLegacyFeatures.INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED) == false) {
                 Request createTestIndex = new Request("PUT", "/" + INDEX_NAME + "/_settings");
                 // update index settings should work
                 createTestIndex.setJsonEntity("{\"index.indexing.slowlog.level\": \"INFO\"}");
@@ -117,7 +117,7 @@ public class UpgradeWithOldIndexSettingsIT extends ParameterizedRollingUpgradeTe
     public static void updateIndexSettingsPermittingSlowlogDeprecationWarning(String index, Settings.Builder settings) throws IOException {
         Request request = new Request("PUT", "/" + index + "/_settings");
         request.setJsonEntity(org.elasticsearch.common.Strings.toString(settings.build()));
-        if (getOldClusterVersion().before(Version.V_7_17_9)) {
+        if (oldClusterHasFeature(RestTestLegacyFeatures.DEPRECATION_WARNINGS_LEAK_FIXED) == false) {
             // There is a bug (fixed in 7.17.9 and 8.7.0 where deprecation warnings could leak into ClusterApplierService#applyChanges)
             // Below warnings are set (and leaking) from an index in this test case
             request.setOptions(expectVersionSpecificWarnings(v -> {

+ 16 - 2
test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java

@@ -131,7 +131,6 @@ import static java.util.Collections.unmodifiableList;
 import static org.elasticsearch.client.RestClient.IGNORE_RESPONSE_CODES_PARAM;
 import static org.elasticsearch.cluster.ClusterState.VERSION_INTRODUCING_TRANSPORT_VERSIONS;
 import static org.elasticsearch.core.Strings.format;
-import static org.elasticsearch.test.rest.TestFeatureService.ALL_FEATURES;
 import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS;
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.containsString;
@@ -232,7 +231,22 @@ public abstract class ESRestTestCase extends ESTestCase {
 
     private static EnumSet<ProductFeature> availableFeatures;
     private static Set<String> nodesVersions;
-    private static TestFeatureService testFeatureService = ALL_FEATURES;
+
+    private static final TestFeatureService ALL_FEATURES = new TestFeatureService() {
+        @Override
+        public boolean clusterHasFeature(String featureId) {
+            return true;
+        }
+
+        @Override
+        public Set<String> getAllSupportedFeatures() {
+            throw new UnsupportedOperationException(
+                "Only available to properly initialized TestFeatureService. See ESRestTestCase#createTestFeatureService"
+            );
+        }
+    };
+
+    protected static TestFeatureService testFeatureService = ALL_FEATURES;
 
     protected static Set<String> getCachedNodesVersions() {
         assert nodesVersions != null;

+ 14 - 0
test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestFeatureService.java

@@ -9,6 +9,7 @@
 package org.elasticsearch.test.rest;
 
 import org.elasticsearch.Version;
+import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.features.FeatureData;
 import org.elasticsearch.features.FeatureSpecification;
 
@@ -22,6 +23,7 @@ import java.util.function.Predicate;
 class ESRestTestFeatureService implements TestFeatureService {
     private final Predicate<String> historicalFeaturesPredicate;
     private final Set<String> clusterStateFeatures;
+    private final Set<String> allSupportedFeatures;
 
     ESRestTestFeatureService(
         List<? extends FeatureSpecification> specs,
@@ -31,6 +33,12 @@ class ESRestTestFeatureService implements TestFeatureService {
         var minNodeVersion = nodeVersions.stream().min(Comparator.naturalOrder());
         var featureData = FeatureData.createFromSpecifications(specs);
         var historicalFeatures = featureData.getHistoricalFeatures();
+        Set<String> allHistoricalFeatures = historicalFeatures.lastEntry() == null ? Set.of() : historicalFeatures.lastEntry().getValue();
+
+        this.allSupportedFeatures = Sets.union(clusterStateFeatures, minNodeVersion.<Set<String>>map(v -> {
+            var historicalFeaturesForVersion = historicalFeatures.floorEntry(v);
+            return historicalFeaturesForVersion == null ? Set.of() : historicalFeaturesForVersion.getValue();
+        }).orElse(allHistoricalFeatures));
 
         this.historicalFeaturesPredicate = minNodeVersion.<Predicate<String>>map(
             v -> featureId -> hasHistoricalFeature(historicalFeatures, v, featureId)
@@ -43,10 +51,16 @@ class ESRestTestFeatureService implements TestFeatureService {
         return features != null && features.getValue().contains(featureId);
     }
 
+    @Override
     public boolean clusterHasFeature(String featureId) {
         if (clusterStateFeatures.contains(featureId)) {
             return true;
         }
         return historicalFeaturesPredicate.test(featureId);
     }
+
+    @Override
+    public Set<String> getAllSupportedFeatures() {
+        return allSupportedFeatures;
+    }
 }

+ 32 - 2
test/framework/src/main/java/org/elasticsearch/test/rest/RestTestLegacyFeatures.java

@@ -57,10 +57,10 @@ public class RestTestLegacyFeatures implements FeatureSpecification {
     public static final NodeFeature ML_MEMORY_OVERHEAD_FIXED = new NodeFeature("ml.memory_overhead_fixed");
 
     // QA - rolling upgrade tests
+    public static final NodeFeature DESIRED_NODE_API_SUPPORTED = new NodeFeature("desired_node_supported");
     public static final NodeFeature SECURITY_UPDATE_API_KEY = new NodeFeature("security.api_key_update");
     public static final NodeFeature SECURITY_BULK_UPDATE_API_KEY = new NodeFeature("security.api_key_bulk_update");
     @UpdateForV9
-
     public static final NodeFeature WATCHES_VERSION_IN_META = new NodeFeature("watcher.version_in_meta");
     @UpdateForV9
     public static final NodeFeature SECURITY_ROLE_DESCRIPTORS_OPTIONAL = new NodeFeature("security.role_descriptors_optional");
@@ -76,6 +76,27 @@ public class RestTestLegacyFeatures implements FeatureSpecification {
     @UpdateForV9
     public static final NodeFeature ML_ANALYTICS_MAPPINGS = new NodeFeature("ml.analytics_mappings");
 
+    public static final NodeFeature TSDB_NEW_INDEX_FORMAT = new NodeFeature("indices.tsdb_new_format");
+    public static final NodeFeature TSDB_GENERALLY_AVAILABLE = new NodeFeature("indices.tsdb_supported");
+
+    /*
+     * A composable index template with no template defined in the body is mistakenly always assumed to not be a time series template.
+     * Fixed in #98840
+     */
+    public static final NodeFeature TSDB_EMPTY_TEMPLATE_FIXED = new NodeFeature("indices.tsdb_empty_composable_template_fixed");
+    public static final NodeFeature SYNTHETIC_SOURCE_SUPPORTED = new NodeFeature("indices.synthetic_source");
+
+    public static final NodeFeature DESIRED_BALANCED_ALLOCATOR_SUPPORTED = new NodeFeature("allocator.desired_balance");
+
+    /*
+     * Cancel shard allocation command is broken for initial desired balance versions
+     * and might allocate shard on the node where it is not supposed to be. This
+     * is fixed by https://github.com/elastic/elasticsearch/pull/93635.
+     */
+    public static final NodeFeature DESIRED_BALANCED_ALLOCATOR_FIXED = new NodeFeature("allocator.desired_balance_fixed");
+    public static final NodeFeature INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED = new NodeFeature("settings.indexing_slowlog_level_removed");
+    public static final NodeFeature DEPRECATION_WARNINGS_LEAK_FIXED = new NodeFeature("deprecation_warnings_leak_fixed");
+
     // YAML
     public static final NodeFeature REST_ELASTIC_PRODUCT_HEADER_PRESENT = new NodeFeature("action.rest.product_header_present");
 
@@ -103,7 +124,16 @@ public class RestTestLegacyFeatures implements FeatureSpecification {
             entry(TRANSFORM_NEW_API_ENDPOINT, Version.V_7_5_0),
             entry(ML_INDICES_HIDDEN, Version.V_7_7_0),
             entry(ML_ANALYTICS_MAPPINGS, Version.V_7_3_0),
-            entry(REST_ELASTIC_PRODUCT_HEADER_PRESENT, Version.V_8_0_1)
+            entry(REST_ELASTIC_PRODUCT_HEADER_PRESENT, Version.V_8_0_1),
+            entry(DESIRED_NODE_API_SUPPORTED, Version.V_8_1_0),
+            entry(TSDB_NEW_INDEX_FORMAT, Version.V_8_2_0),
+            entry(SYNTHETIC_SOURCE_SUPPORTED, Version.V_8_4_0),
+            entry(DESIRED_BALANCED_ALLOCATOR_SUPPORTED, Version.V_8_6_0),
+            entry(DESIRED_BALANCED_ALLOCATOR_FIXED, Version.V_8_7_1),
+            entry(TSDB_GENERALLY_AVAILABLE, Version.V_8_7_0),
+            entry(TSDB_EMPTY_TEMPLATE_FIXED, Version.V_8_11_0),
+            entry(INDEXING_SLOWLOG_LEVEL_SETTING_REMOVED, Version.V_8_0_0),
+            entry(DEPRECATION_WARNINGS_LEAK_FIXED, Version.V_7_17_9)
         );
     }
 }

+ 3 - 1
test/framework/src/main/java/org/elasticsearch/test/rest/TestFeatureService.java

@@ -8,8 +8,10 @@
 
 package org.elasticsearch.test.rest;
 
+import java.util.Set;
+
 public interface TestFeatureService {
     boolean clusterHasFeature(String featureId);
 
-    TestFeatureService ALL_FEATURES = ignored -> true;
+    Set<String> getAllSupportedFeatures();
 }

+ 5 - 0
test/yaml-rest-runner/src/test/java/org/elasticsearch/test/rest/yaml/ClientYamlTestExecutionContextTests.java

@@ -29,6 +29,11 @@ public class ClientYamlTestExecutionContextTests extends ESTestCase {
         public boolean clusterHasFeature(String featureId) {
             return true;
         }
+
+        @Override
+        public Set<String> getAllSupportedFeatures() {
+            return Set.of();
+        }
     }
 
     public void testHeadersSupportStashedValueReplacement() throws IOException {