Browse Source

DesiredNode: deprecate `node_version` field and make it optional (unused) in current parser (#104209)

Deprecated node_version field, made it optional(unused) in new parser
Added deprecation warning handler for mixed cluster
Split tests for old vs. current format
Lorenzo Dematté 1 year ago
parent
commit
0e328dbfc8
27 changed files with 647 additions and 513 deletions
  1. 13 0
      docs/changelog/104209.yaml
  2. 1 2
      docs/reference/cluster/delete-desired-nodes.asciidoc
  3. 2 4
      docs/reference/cluster/get-desired-nodes.asciidoc
  4. 3 6
      docs/reference/cluster/update-desired-nodes.asciidoc
  5. 11 6
      qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DesiredNodesUpgradeIT.java
  6. 160 203
      rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/10_basic.yml
  7. 283 0
      rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/11_old_format.yml
  8. 16 9
      rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/20_dry_run.yml
  9. 7 34
      server/src/internalClusterTest/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportDesiredNodesActionsIT.java
  10. 1 3
      server/src/internalClusterTest/java/org/elasticsearch/cluster/DesiredNodesSnapshotsIT.java
  11. 1 0
      server/src/main/java/org/elasticsearch/TransportVersions.java
  12. 1 1
      server/src/main/java/org/elasticsearch/action/ActionModule.java
  13. 8 33
      server/src/main/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportUpdateDesiredNodesAction.java
  14. 0 74
      server/src/main/java/org/elasticsearch/cluster/desirednodes/DesiredNodesSettingsValidator.java
  15. 69 24
      server/src/main/java/org/elasticsearch/cluster/metadata/DesiredNode.java
  16. 1 2
      server/src/main/java/org/elasticsearch/cluster/metadata/DesiredNodeWithStatus.java
  17. 6 0
      server/src/main/java/org/elasticsearch/cluster/metadata/MetadataFeatures.java
  18. 25 0
      server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestUpdateDesiredNodesAction.java
  19. 0 2
      server/src/test/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportUpdateDesiredNodesActionTests.java
  20. 2 4
      server/src/test/java/org/elasticsearch/action/admin/cluster/desirednodes/UpdateDesiredNodesRequestTests.java
  21. 0 35
      server/src/test/java/org/elasticsearch/cluster/desirednodes/DesiredNodesSettingsValidatorTests.java
  22. 6 19
      server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodeSerializationTests.java
  23. 21 30
      server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodeTests.java
  24. 7 14
      server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodesTestCase.java
  25. 1 3
      server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodesTests.java
  26. 1 2
      x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDeciderIT.java
  27. 1 3
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDeciderTests.java

+ 13 - 0
docs/changelog/104209.yaml

@@ -0,0 +1,13 @@
+pr: 104209
+summary: '`DesiredNode:` deprecate `node_version` field and make it optional (unused)
+  in current parser'
+area: Distributed
+type: deprecation
+issues: []
+deprecation:
+  title: '`DesiredNode:` deprecate `node_version` field and make it optional for the current version'
+  area: REST API
+  details: The desired_node API includes a `node_version` field to perform validation on the new node version required.
+    This kind of check is too broad, and it's better done by external logic, so it has been removed, making the
+    `node_version` field not necessary. The field will be removed in a later version.
+  impact: Users should update their usages of `desired_node` to not include the `node_version` field anymore.

+ 1 - 2
docs/reference/cluster/delete-desired-nodes.asciidoc

@@ -27,8 +27,7 @@ PUT /_internal/desired_nodes/history/1
             },
             "processors" : 8.0,
             "memory" : "58gb",
-            "storage" : "2tb",
-            "node_version" : "{version}"
+            "storage" : "2tb"
         }
     ]
 }

+ 2 - 4
docs/reference/cluster/get-desired-nodes.asciidoc

@@ -27,8 +27,7 @@ PUT /_internal/desired_nodes/my_history/1
             },
             "processors" : 8.0,
             "memory" : "59gb",
-            "storage" : "2tb",
-            "node_version" : "{version}"
+            "storage" : "2tb"
         }
     ]
 }
@@ -79,8 +78,7 @@ The API returns the following result:
             "settings": <node_settings>,
             "processors": <node_processors>,
             "memory": "<node_memory>",
-            "storage": "<node_storage>",
-            "node_version": "<node_version>"
+            "storage": "<node_storage>"
         }
     ]
 }

+ 3 - 6
docs/reference/cluster/update-desired-nodes.asciidoc

@@ -26,8 +26,7 @@ PUT /_internal/desired_nodes/<history_id>/<version>
             },
             "processors" : 8.0,
             "memory" : "58gb",
-            "storage" : "2tb",
-            "node_version" : "{version}"
+            "storage" : "2tb"
         }
     ]
 }
@@ -86,8 +85,7 @@ PUT /_internal/desired_nodes/Ywkh3INLQcuPT49f6kcppA/100
             },
             "processors" : 8.0,
             "memory" : "58gb",
-            "storage" : "2tb",
-            "node_version" : "{version}"
+            "storage" : "2tb"
         }
     ]
 }
@@ -125,8 +123,7 @@ PUT /_internal/desired_nodes/Ywkh3INLQcuPT49f6kcppA/101
             },
             "processors_range" : {"min": 8.0, "max": 10.0},
             "memory" : "58gb",
-            "storage" : "2tb",
-            "node_version" : "{version}"
+            "storage" : "2tb"
         }
     ]
 }

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

@@ -10,7 +10,7 @@ package org.elasticsearch.upgrades;
 
 import com.carrotsearch.randomizedtesting.annotations.Name;
 
-import org.elasticsearch.Version;
+import org.elasticsearch.Build;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesRequest;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.ResponseException;
@@ -82,12 +82,12 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
                     1238.49922909,
                     ByteSizeValue.ofGb(32),
                     ByteSizeValue.ofGb(128),
-                    Version.CURRENT
+                    clusterHasFeature(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED) ? null : Build.current().version()
                 )
             )
             .toList();
 
-        if (isMixedCluster() || isUpgradedCluster()) {
+        if (isMixedCluster()) {
             updateDesiredNodes(desiredNodes, desiredNodesVersion - 1);
         }
         for (int i = 0; i < 2; i++) {
@@ -153,7 +153,7 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
                         processorsPrecision == ProcessorsPrecision.DOUBLE ? randomDoubleProcessorCount() : 0.5f,
                         ByteSizeValue.ofGb(randomIntBetween(10, 24)),
                         ByteSizeValue.ofGb(randomIntBetween(128, 256)),
-                        Version.CURRENT
+                        clusterHasFeature(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED) ? null : Build.current().version()
                     )
                 )
                 .toList();
@@ -167,7 +167,7 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
                     new DesiredNode.ProcessorsRange(minProcessors, minProcessors + randomIntBetween(10, 20)),
                     ByteSizeValue.ofGb(randomIntBetween(10, 24)),
                     ByteSizeValue.ofGb(randomIntBetween(128, 256)),
-                    Version.CURRENT
+                    clusterHasFeature(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED) ? null : Build.current().version()
                 );
             }).toList();
         }
@@ -182,7 +182,7 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
                     randomIntBetween(1, 24),
                     ByteSizeValue.ofGb(randomIntBetween(10, 24)),
                     ByteSizeValue.ofGb(randomIntBetween(128, 256)),
-                    Version.CURRENT
+                    clusterHasFeature(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED) ? null : Build.current().version()
                 )
             )
             .toList();
@@ -196,6 +196,11 @@ public class DesiredNodesUpgradeIT extends ParameterizedRollingUpgradeTestCase {
             builder.xContentList(UpdateDesiredNodesRequest.NODES_FIELD.getPreferredName(), nodes);
             builder.endObject();
             request.setJsonEntity(Strings.toString(builder));
+            request.setOptions(
+                expectVersionSpecificWarnings(
+                    v -> v.compatible("[version removal] Specifying node_version in desired nodes requests is deprecated.")
+                )
+            );
             final var response = client().performRequest(request);
             final var statusCode = response.getStatusLine().getStatusCode();
             assertThat(statusCode, equalTo(200));

+ 160 - 203
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/10_basic.yml

@@ -1,8 +1,8 @@
 ---
 setup:
   - skip:
-      version: " - 8.2.99"
-      reason: "API added in in 8.1.0 but modified in 8.3"
+      version: " - 8.12.99"
+      reason: "API added in in 8.1.0 but modified in 8.13 (node_version field removed)"
 ---
 teardown:
   - do:
@@ -22,6 +22,57 @@ teardown:
       nodes.info: {}
   - set: { nodes.$master.version: es_version }
 
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match:
+      $body:
+        history_id: "test"
+        version: 1
+        nodes:
+          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb" }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 2
+        body:
+          nodes:
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.name": "instance-000188" }, processors: 16.0, memory: "128gb", storage: "1tb" }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+
+  - match: { history_id: "test" }
+  - match: { version: 2 }
+  - length: { nodes: 2 }
+  - contains: { nodes: { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { name: "instance-000188" } }, processors: 16.0, memory: "128gb", storage: "1tb" } }
+---
+"Test update desired nodes with node_version generates a warning":
+  - skip:
+      reason: "contains is a newly added assertion"
+      features: ["contains", "allowed_warnings"]
+  - do:
+      cluster.state: {}
+
+  # Get master node id
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
   - do:
       _internal.update_desired_nodes:
         history_id: "test"
@@ -29,6 +80,8 @@ teardown:
         body:
             nodes:
               - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+      allowed_warnings:
+        - "[version removal] Specifying node_version in desired nodes requests is deprecated."
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -48,6 +101,8 @@ teardown:
           nodes:
             - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
             - { settings: { "node.name": "instance-000188" }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version }
+      allowed_warnings:
+        - "[version removal] Specifying node_version in desired nodes requests is deprecated."
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -78,7 +133,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -88,7 +143,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" }
 
   - do:
       _internal.update_desired_nodes:
@@ -96,8 +151,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000188" }, processors: 16, memory: "128gb", storage: "1tb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 16, memory: "128gb", storage: "1tb" }
   - match: { replaced_existing_history_id: true }
 
   - do:
@@ -105,8 +160,8 @@ teardown:
   - match: { history_id: "new_history" }
   - match: { version: 1 }
   - length: { nodes: 2 }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 16.0, memory: "128gb", storage: "1tb" } }
 ---
 "Test delete desired nodes":
   - do:
@@ -118,6 +173,44 @@ teardown:
       nodes.info: {}
   - set: { nodes.$master.version: es_version }
 
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match:
+      $body:
+        history_id: "test"
+        version: 1
+        nodes:
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" }
+
+  - do:
+      _internal.delete_desired_nodes: {}
+
+  - do:
+      catch: missing
+      _internal.get_desired_nodes: {}
+  - match: { status: 404 }
+---
+"Test delete desired nodes with node_version generates a warning":
+  - skip:
+      features: allowed_warnings
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
   - do:
       _internal.update_desired_nodes:
         history_id: "test"
@@ -125,6 +218,8 @@ teardown:
         body:
           nodes:
             - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+      allowed_warnings:
+        - "[version removal] Specifying node_version in desired nodes requests is deprecated."
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -163,8 +258,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -173,8 +268,8 @@ teardown:
   - match: { history_id: "test" }
   - match: { version: 1 }
   - length: { nodes: 2 }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
 
   - do:
       _internal.update_desired_nodes:
@@ -182,8 +277,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb" }
 
   - match: { replaced_existing_history_id: false }
 
@@ -193,8 +288,8 @@ teardown:
   - match: { history_id: "test" }
   - match: { version: 1 }
   - length: { nodes: 2 }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
 ---
 "Test update desired nodes is idempotent with different order":
   - skip:
@@ -215,8 +310,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -225,8 +320,8 @@ teardown:
   - match: { history_id: "test" }
   - match: { version: 1 }
   - length: { nodes: 2 }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
 
   - do:
       _internal.update_desired_nodes:
@@ -234,8 +329,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 8.0, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
 
   - match: { replaced_existing_history_id: false }
 
@@ -245,8 +340,8 @@ teardown:
   - match: { history_id: "test" }
   - match: { version: 1 }
   - length: { nodes: 2 }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
-  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 8.0, memory: "64gb", storage: "128gb" } }
 ---
 "Test going backwards within the same history is forbidden":
   - do:
@@ -264,7 +359,7 @@ teardown:
         version: 2
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -274,7 +369,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187", "http.tcp.keep_idle": 100 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187", "http.tcp.keep_idle": 100 }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 409 }
   - match: { error.type: version_conflict_exception }
   - match: { error.reason: "version [1] has been superseded by version [2] for history [test]" }
@@ -286,7 +381,7 @@ teardown:
         history_id: "test"
         version: 2
         nodes:
-          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" }
 ---
 "Test using the same version with different definition is forbidden":
   - do:
@@ -304,7 +399,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -314,7 +409,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 64.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 64.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: illegal_argument_exception }
   - match: { error.reason: "Desired nodes with history [test] and version [1] already exists with a different definition" }
@@ -326,94 +421,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
----
-"Test settings are validated":
-  - skip:
-      version: "8.9.99 - "
-      reason: "We started skipping setting validations in 8.10"
-  - do:
-      cluster.state: {}
-
-  - set: { master_node: master }
-
-  - do:
-      nodes.info: {}
-  - set: { nodes.$master.version: es_version }
-
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187", "http.tcp.keep_idle": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-  - match: { status: 400 }
-  - match: { error.type: illegal_argument_exception }
-  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
-  - match: { error.suppressed.0.reason: "Failed to parse value [-1000] for setting [http.tcp.keep_idle] must be >= -1" }
----
-"Test unknown settings are forbidden in known versions":
-  - skip:
-      version: "8.9.99 - "
-      reason: "We started skipping setting validations in 8.10"
-  - do:
-      cluster.state: {}
-
-  - set: { master_node: master }
-
-  - do:
-      nodes.info: {}
-  - set: { nodes.$master.version: es_version }
-
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187", "unknown_setting": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-  - match: { status: 400 }
-  - match: { error.type: illegal_argument_exception }
-  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
-  - match: { error.suppressed.0.reason: "unknown setting [unknown_setting] please check that any required plugins are installed, or check the breaking changes documentation for removed settings" }
----
-"Test unknown settings are allowed in future versions":
-  - skip:
-      version: "8.9.99 - "
-      reason: "We started skipping setting validations in 8.10"
-  - do:
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187", "unknown_setting": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: "99.1.0" }
-  - match: { replaced_existing_history_id: false }
----
-"Test some settings can be overridden":
-  - skip:
-      version: "8.9.99 - "
-      reason: "We started skipping setting validations in 8.10"
-  - do:
-      cluster.state: {}
-
-  - set: { master_node: master }
-
-  - do:
-      nodes.info: {}
-  - set: { nodes.$master.version: es_version }
-
-  - do:
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187", node.processors: 2048 }, processors: 2048, memory: "64gb", storage: "128gb", node_version: $es_version }
-  - match: { replaced_existing_history_id: false }
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb" }
 ---
 "Test external_id or node.name is required":
   - do:
@@ -432,7 +440,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
   - match: { error.caused_by.caused_by.reason: "[node.name] or [node.external_id] is missing or empty" }
@@ -454,7 +462,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "  " }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "  " }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
   - match: { error.caused_by.caused_by.reason: "[node.name] or [node.external_id] is missing or empty" }
@@ -476,8 +484,8 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.external_id": "instance-000187"}, processors: 16.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.external_id": "instance-000187"}, processors: 16.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: illegal_argument_exception }
   - match: { error.reason: "Some nodes contain the same setting value [instance-000187] for [node.external_id]" }
@@ -499,7 +507,7 @@ teardown:
         version: "asa"
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: illegal_argument_exception }
   - match: { error.reason: "Failed to parse long parameter [version] with value [asa]" }
@@ -521,26 +529,11 @@ teardown:
         version: -1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: action_request_validation_exception }
   - match: { error.reason: "Validation Failed: 1: version must be positive;" }
 ---
-"Test node version must be at least the current master version":
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb", node_version: "7.16.0" }
-  - match: { status: 400 }
-  - match: { error.type: illegal_argument_exception }
-  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
-  - match:
-      error.suppressed.0.reason: "/Illegal\\snode\\sversion.+$/"
----
 "Test history_id must be present":
   - do:
       cluster.state: {}
@@ -558,7 +551,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: action_request_validation_exception }
   - match: { error.reason: "Validation Failed: 1: historyID should not be empty;" }
@@ -592,7 +585,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.external_id": "instance-000187", "node.roles": "data_hot" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000187", "node.roles": "data_hot" }, processors: 8.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: action_request_validation_exception }
   - match: { error.reason: "Validation Failed: 1: nodes must contain at least one master node;" }
@@ -614,7 +607,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { processors: 64.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { processors: 64.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -635,7 +628,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: null, processors: 64.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: null, processors: 64.0, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -656,7 +649,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: {}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: {}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -677,7 +670,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: {}, processors: 8.0, storage: "128gb", node_version: $es_version }
+            - { settings: {}, processors: 8.0, storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -698,7 +691,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: {}, processors: 8.0, memory: null, storage: "128gb", node_version: $es_version }
+            - { settings: {}, processors: 8.0, memory: null, storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -719,7 +712,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: {}, processors: 8.0, memory: "64gb", node_version: $es_version }
+            - { settings: {}, processors: 8.0, memory: "64gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -740,43 +733,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: {}, processors: 8, memory: "64gb", storage: null, node_version: $es_version }
-  - match: { status: 400 }
-  - match: { error.type: x_content_parse_exception }
----
-"Test node version is required":
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: {}, processors: 8, memory: "64gb", storage: "128gb" }
-  - match: { status: 400 }
-  - match: { error.type: x_content_parse_exception }
----
-"Test node version must have content":
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 64, memory: "1b", storage: "1b", node_version: " " }
-  - match: { status: 400 }
-  - match: { error.type: x_content_parse_exception }
----
-"Test node version can not be null":
-  - do:
-      catch: bad_request
-      _internal.update_desired_nodes:
-        history_id: "test"
-        version: 1
-        body:
-          nodes:
-            - { settings: { "node.external_id": "instance-000187"}, processors: 64, memory: "1b", storage: "1b", node_version: null }
+            - { settings: {}, processors: 8, memory: "64gb", storage: null }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -797,7 +754,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 16.0, max: 20.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 16.0, max: 20.0}, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
 
   - do:
@@ -807,7 +764,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { name: "instance-000187" } }, processors_range: {min: 16.0, max: 20.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { name: "instance-000187" } }, processors_range: {min: 16.0, max: 20.0}, memory: "64gb", storage: "128gb" }
 ---
 "Test processors min and max are required":
   - do:
@@ -827,7 +784,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: { }, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: { }, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -849,7 +806,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {max: 8.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {max: 8.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -870,7 +827,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 8.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 8.0}, memory: "64gb", storage: "128gb" }
 
   - do:
       _internal.get_desired_nodes: {}
@@ -879,7 +836,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { name: "instance-000187" } }, processors_range: {min: 8.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { name: "instance-000187" } }, processors_range: {min: 8.0}, memory: "64gb", storage: "128gb" }
 ---
 "Test min processors should be less than or equal to max processors":
   - do:
@@ -899,7 +856,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 8.0, max: 1.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 8.0, max: 1.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -921,7 +878,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: NaN, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: NaN, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -943,7 +900,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: Infinity, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: Infinity, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -965,7 +922,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: -Infinity, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: -Infinity, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -987,7 +944,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: NaN, max: 1.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: NaN, max: 1.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1009,7 +966,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: Infinity, max: 1.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: Infinity, max: 1.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1031,7 +988,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: -Infinity, max: 1.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: -Infinity, max: 1.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1053,7 +1010,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 0.0, max: 1.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 0.0, max: 1.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1075,7 +1032,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: NaN}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: NaN}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1097,7 +1054,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: Infinity}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: Infinity}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1119,7 +1076,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: -Infinity}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: -Infinity}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1141,7 +1098,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: 0.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors_range: {min: 1.0, max: 0.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1163,7 +1120,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: 1.0, processors_range: {min: 1.0, max: 2.0}, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: 1.0, processors_range: {min: 1.0, max: 2.0}, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }
 ---
@@ -1185,6 +1142,6 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187", "node.roles": "unknown,other" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187", "node.roles": "unknown,other" }, processors: 8.5, memory: "64gb", storage: "128gb" }
   - match: { status: 400 }
   - match: { error.type: x_content_parse_exception }

+ 283 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/11_old_format.yml

@@ -0,0 +1,283 @@
+---
+setup:
+  - skip:
+      version: " - 8.2.99, 8.12.99 - "
+      reason: "API added in in 8.1.0, modified in 8.3 and then again in 8.13.0"
+---
+teardown:
+  - do:
+      _internal.delete_desired_nodes: {}
+---
+"Test update desired nodes":
+  - skip:
+      reason: "contains is a newly added assertion"
+      features: contains
+  - do:
+      cluster.state: {}
+
+  # Get master node id
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+            nodes:
+              - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match:
+      $body:
+        history_id: "test"
+        version: 1
+        nodes:
+          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 2
+        body:
+          nodes:
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000188" }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+
+  - match: { history_id: "test" }
+  - match: { version: 2 }
+  - length: { nodes: 2 }
+  - contains: { nodes: { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { name: "instance-000188" } }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version } }
+---
+"Test update move to a new history id":
+  - skip:
+      reason: "contains is a newly added assertion"
+      features: contains
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match:
+      $body:
+        history_id: "test"
+        version: 1
+        nodes:
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "new_history"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.external_id": "instance-000188" }, processors: 16, memory: "128gb", storage: "1tb", node_version: $es_version }
+  - match: { replaced_existing_history_id: true }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match: { history_id: "new_history" }
+  - match: { version: 1 }
+  - length: { nodes: 2 }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version } }
+  - contains: { nodes: { settings: { node: { external_id: "instance-000188" } }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version } }
+---
+"Test delete desired nodes":
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187" }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { replaced_existing_history_id: false }
+
+  - do:
+      _internal.get_desired_nodes: {}
+  - match:
+      $body:
+        history_id: "test"
+        version: 1
+        nodes:
+          - { settings: { node: { external_id: "instance-000187" } }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+
+  - do:
+      _internal.delete_desired_nodes: {}
+
+  - do:
+      catch: missing
+      _internal.get_desired_nodes: {}
+  - match: { status: 404 }
+---
+"Test settings are validated":
+  - skip:
+      version: "8.9.99 - "
+      reason: "We started skipping setting validations in 8.10"
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187", "http.tcp.keep_idle": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { status: 400 }
+  - match: { error.type: illegal_argument_exception }
+  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
+  - match: { error.suppressed.0.reason: "Failed to parse value [-1000] for setting [http.tcp.keep_idle] must be >= -1" }
+---
+"Test unknown settings are forbidden in known versions":
+  - skip:
+      version: "8.9.99 - "
+      reason: "We started skipping setting validations in 8.10"
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187", "unknown_setting": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { status: 400 }
+  - match: { error.type: illegal_argument_exception }
+  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
+  - match: { error.suppressed.0.reason: "unknown setting [unknown_setting] please check that any required plugins are installed, or check the breaking changes documentation for removed settings" }
+---
+"Test unknown settings are allowed in future versions":
+  - skip:
+      version: "8.9.99 - "
+      reason: "We started skipping setting validations in 8.10"
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187", "unknown_setting": -1000 }, processors: 8.0, memory: "64gb", storage: "128gb", node_version: "99.1.0" }
+  - match: { replaced_existing_history_id: false }
+---
+"Test some settings can be overridden":
+  - skip:
+      version: "8.9.99 - "
+      reason: "We started skipping setting validations in 8.10"
+  - do:
+      cluster.state: {}
+
+  - set: { master_node: master }
+
+  - do:
+      nodes.info: {}
+  - set: { nodes.$master.version: es_version }
+
+  - do:
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187", node.processors: 2048 }, processors: 2048, memory: "64gb", storage: "128gb", node_version: $es_version }
+  - match: { replaced_existing_history_id: false }
+---
+"Test node version must be at least the current master version":
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187"}, processors: 8.0, memory: "64gb", storage: "128gb", node_version: "7.16.0" }
+  - match: { status: 400 }
+  - match: { error.type: illegal_argument_exception }
+  - match: { error.reason: "Nodes with ids [instance-000187] in positions [0] contain invalid settings" }
+  - match:
+      error.suppressed.0.reason: "/Illegal\\snode\\sversion.+$/"
+---
+"Test node version is required":
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: {}, processors: 8, memory: "64gb", storage: "128gb" }
+  - match: { status: 400 }
+  - match: { error.type: x_content_parse_exception }
+---
+"Test node version must have content":
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187"}, processors: 64, memory: "1b", storage: "1b", node_version: " " }
+  - match: { status: 400 }
+  - match: { error.type: x_content_parse_exception }
+---
+"Test node version can not be null":
+  - do:
+      catch: bad_request
+      _internal.update_desired_nodes:
+        history_id: "test"
+        version: 1
+        body:
+          nodes:
+            - { settings: { "node.external_id": "instance-000187"}, processors: 64, memory: "1b", storage: "1b", node_version: null }
+  - match: { status: 400 }
+  - match: { error.type: x_content_parse_exception }

+ 16 - 9
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_nodes/20_dry_run.yml

@@ -10,6 +10,9 @@ teardown:
 
 ---
 "Test dry run doesn't update empty desired nodes":
+  - skip:
+      version: " - 8.12.99"
+      reason: "version_node removed from version 8.13 onwards"
   - do:
       cluster.state: {}
 
@@ -26,7 +29,7 @@ teardown:
         dry_run: true
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
   - match: { dry_run: true }
 
@@ -37,6 +40,9 @@ teardown:
 
 ---
 "Test dry run doesn't update existing desired nodes":
+  - skip:
+      version: " - 8.12.99"
+      reason: "version_node removed from version 8.13 onwards"
   - do:
       cluster.state: {}
 
@@ -52,7 +58,7 @@ teardown:
         version: 1
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }
   - match: { replaced_existing_history_id: false }
   - match: { dry_run: false }
 
@@ -63,7 +69,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb" }
 
   - do:
       _internal.update_desired_nodes:
@@ -72,8 +78,8 @@ teardown:
         dry_run: "true"
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
-            - { settings: { "node.name": "instance-000188" }, processors: 16.0, memory: "128gb", storage: "1tb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }
+            - { settings: { "node.name": "instance-000188" }, processors: 16.0, memory: "128gb", storage: "1tb" }
   - match: { replaced_existing_history_id: false }
   - match: { dry_run: true }
 
@@ -84,7 +90,7 @@ teardown:
         history_id: "test"
         version: 1
         nodes:
-          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+          - { settings: { node: { name: "instance-000187" } }, processors: 8.5, memory: "64gb", storage: "128gb" }
 ---
 "Test validation works for dry run updates":
   - skip:
@@ -112,10 +118,11 @@ teardown:
   - match: { error.type: illegal_argument_exception }
   - match: { error.reason: "Nodes with ids [instance-000245] in positions [0] contain invalid settings" }
   - match: { error.suppressed.0.reason: "unknown setting [random_setting] please check that any required plugins are installed, or check the breaking changes documentation for removed settings" }
-
-
 ---
 "Test misspelled dry run":
+  - skip:
+      version: " - 8.12.99"
+      reason: "version_node removed from version 8.13 onwards"
   - do:
       cluster.state: { }
 
@@ -133,4 +140,4 @@ teardown:
         diy_run: "true"
         body:
           nodes:
-            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb", node_version: $es_version }
+            - { settings: { "node.name": "instance-000187" }, processors: 8.5, memory: "64gb", storage: "128gb" }

+ 7 - 34
server/src/internalClusterTest/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportDesiredNodesActionsIT.java

@@ -9,7 +9,6 @@
 package org.elasticsearch.action.admin.cluster.desirednodes;
 
 import org.elasticsearch.ResourceNotFoundException;
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionFuture;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
@@ -60,23 +59,19 @@ public class TransportDesiredNodesActionsIT extends ESIntegTestCase {
     }
 
     public void testDryRunUpdateDoesNotUpdateEmptyDesiredNodes() {
-        UpdateDesiredNodesResponse dryRunResponse = updateDesiredNodes(
-            randomDryRunUpdateDesiredNodesRequest(Version.CURRENT, Settings.EMPTY)
-        );
+        UpdateDesiredNodesResponse dryRunResponse = updateDesiredNodes(randomDryRunUpdateDesiredNodesRequest(Settings.EMPTY));
         assertThat(dryRunResponse.dryRun(), is(equalTo(true)));
 
         expectThrows(ResourceNotFoundException.class, this::getLatestDesiredNodes);
     }
 
     public void testDryRunUpdateDoesNotUpdateExistingDesiredNodes() {
-        UpdateDesiredNodesResponse response = updateDesiredNodes(randomUpdateDesiredNodesRequest(Version.CURRENT, Settings.EMPTY));
+        UpdateDesiredNodesResponse response = updateDesiredNodes(randomUpdateDesiredNodesRequest(Settings.EMPTY));
         assertThat(response.dryRun(), is(equalTo(false)));
 
         DesiredNodes desiredNodes = getLatestDesiredNodes();
 
-        UpdateDesiredNodesResponse dryRunResponse = updateDesiredNodes(
-            randomDryRunUpdateDesiredNodesRequest(Version.CURRENT, Settings.EMPTY)
-        );
+        UpdateDesiredNodesResponse dryRunResponse = updateDesiredNodes(randomDryRunUpdateDesiredNodesRequest(Settings.EMPTY));
         assertThat(dryRunResponse.dryRun(), is(equalTo(true)));
 
         assertEquals(getLatestDesiredNodes(), desiredNodes);
@@ -183,7 +178,6 @@ public class TransportDesiredNodesActionsIT extends ESIntegTestCase {
 
     public void testUnknownSettingsAreAllowedInFutureVersions() {
         final var updateDesiredNodesRequest = randomUpdateDesiredNodesRequest(
-            Version.fromString("99.9.0"),
             Settings.builder().put("desired_nodes.random_setting", Integer.MIN_VALUE).build()
         );
 
@@ -203,11 +197,7 @@ public class TransportDesiredNodesActionsIT extends ESIntegTestCase {
             randomList(
                 1,
                 20,
-                () -> randomDesiredNode(
-                    Version.CURRENT,
-                    Settings.builder().put(NODE_PROCESSORS_SETTING.getKey(), numProcessors).build(),
-                    numProcessors
-                )
+                () -> randomDesiredNode(Settings.builder().put(NODE_PROCESSORS_SETTING.getKey(), numProcessors).build(), numProcessors)
             ),
             false
         );
@@ -224,19 +214,6 @@ public class TransportDesiredNodesActionsIT extends ESIntegTestCase {
         }
     }
 
-    public void testNodeVersionIsValidated() {
-        final var updateDesiredNodesRequest = randomUpdateDesiredNodesRequest(Version.CURRENT.previousMajor(), Settings.EMPTY);
-
-        final IllegalArgumentException exception = expectThrows(
-            IllegalArgumentException.class,
-            () -> updateDesiredNodes(updateDesiredNodesRequest)
-        );
-        assertThat(exception.getMessage(), containsString("Nodes with ids"));
-        assertThat(exception.getMessage(), containsString("contain invalid settings"));
-        assertThat(exception.getSuppressed().length > 0, is(equalTo(true)));
-        assertThat(exception.getSuppressed()[0].getMessage(), containsString("Illegal node version"));
-    }
-
     public void testUpdateDesiredNodesTasksAreBatchedCorrectly() throws Exception {
         final Runnable unblockClusterStateUpdateThread = blockClusterStateUpdateThread();
 
@@ -326,23 +303,19 @@ public class TransportDesiredNodesActionsIT extends ESIntegTestCase {
     }
 
     private UpdateDesiredNodesRequest randomUpdateDesiredNodesRequest(Settings settings) {
-        return randomUpdateDesiredNodesRequest(Version.CURRENT, settings);
-    }
-
-    private UpdateDesiredNodesRequest randomUpdateDesiredNodesRequest(Version version, Settings settings) {
         return new UpdateDesiredNodesRequest(
             UUIDs.randomBase64UUID(),
             randomIntBetween(2, 20),
-            randomList(2, 10, () -> randomDesiredNode(version, settings)),
+            randomList(2, 10, () -> randomDesiredNode(settings)),
             false
         );
     }
 
-    private UpdateDesiredNodesRequest randomDryRunUpdateDesiredNodesRequest(Version version, Settings settings) {
+    private UpdateDesiredNodesRequest randomDryRunUpdateDesiredNodesRequest(Settings settings) {
         return new UpdateDesiredNodesRequest(
             UUIDs.randomBase64UUID(),
             randomIntBetween(2, 20),
-            randomList(2, 10, () -> randomDesiredNode(version, settings)),
+            randomList(2, 10, () -> randomDesiredNode(settings)),
             true
         );
     }

+ 1 - 3
server/src/internalClusterTest/java/org/elasticsearch/cluster/DesiredNodesSnapshotsIT.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.cluster;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.admin.cluster.desirednodes.GetDesiredNodesAction;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesAction;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesRequest;
@@ -70,8 +69,7 @@ public class DesiredNodesSnapshotsIT extends AbstractSnapshotIntegTestCase {
                     Settings.builder().put(NODE_NAME_SETTING.getKey(), randomAlphaOfLength(10)).build(),
                     randomIntBetween(1, 10),
                     ByteSizeValue.ofGb(randomIntBetween(16, 64)),
-                    ByteSizeValue.ofGb(randomIntBetween(128, 256)),
-                    Version.CURRENT
+                    ByteSizeValue.ofGb(randomIntBetween(128, 256))
                 )
             ),
             false

+ 1 - 0
server/src/main/java/org/elasticsearch/TransportVersions.java

@@ -191,6 +191,7 @@ public class TransportVersions {
     public static final TransportVersion NESTED_KNN_MORE_INNER_HITS = def(8_577_00_0);
     public static final TransportVersion REQUIRE_DATA_STREAM_ADDED = def(8_578_00_0);
     public static final TransportVersion ML_INFERENCE_COHERE_EMBEDDINGS_ADDED = def(8_579_00_0);
+    public static final TransportVersion DESIRED_NODE_VERSION_OPTIONAL_STRING = def(8_580_00_0);
 
     /*
      * STOP! READ THIS FIRST! No, really,

+ 1 - 1
server/src/main/java/org/elasticsearch/action/ActionModule.java

@@ -1005,7 +1005,7 @@ public class ActionModule extends AbstractModule {
 
         // Desired nodes
         registerHandler.accept(new RestGetDesiredNodesAction());
-        registerHandler.accept(new RestUpdateDesiredNodesAction());
+        registerHandler.accept(new RestUpdateDesiredNodesAction(clusterSupportsFeature));
         registerHandler.accept(new RestDeleteDesiredNodesAction());
 
         for (ActionPlugin plugin : actionPlugins) {

+ 8 - 33
server/src/main/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportUpdateDesiredNodesAction.java

@@ -18,7 +18,6 @@ import org.elasticsearch.cluster.ClusterStateTaskExecutor;
 import org.elasticsearch.cluster.ClusterStateTaskListener;
 import org.elasticsearch.cluster.block.ClusterBlockException;
 import org.elasticsearch.cluster.block.ClusterBlockLevel;
-import org.elasticsearch.cluster.desirednodes.DesiredNodesSettingsValidator;
 import org.elasticsearch.cluster.desirednodes.VersionConflictException;
 import org.elasticsearch.cluster.metadata.DesiredNode;
 import org.elasticsearch.cluster.metadata.DesiredNodes;
@@ -36,9 +35,7 @@ import org.elasticsearch.tasks.Task;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
-import java.util.List;
 import java.util.Locale;
-import java.util.function.Consumer;
 
 import static java.lang.String.format;
 
@@ -47,7 +44,6 @@ public class TransportUpdateDesiredNodesAction extends TransportMasterNodeAction
 
     private final RerouteService rerouteService;
     private final FeatureService featureService;
-    private final Consumer<List<DesiredNode>> desiredNodesValidator;
     private final MasterServiceTaskQueue<UpdateDesiredNodesTask> taskQueue;
 
     @Inject
@@ -60,30 +56,6 @@ public class TransportUpdateDesiredNodesAction extends TransportMasterNodeAction
         ActionFilters actionFilters,
         IndexNameExpressionResolver indexNameExpressionResolver,
         AllocationService allocationService
-    ) {
-        this(
-            transportService,
-            clusterService,
-            rerouteService,
-            featureService,
-            threadPool,
-            actionFilters,
-            indexNameExpressionResolver,
-            new DesiredNodesSettingsValidator(),
-            allocationService
-        );
-    }
-
-    TransportUpdateDesiredNodesAction(
-        TransportService transportService,
-        ClusterService clusterService,
-        RerouteService rerouteService,
-        FeatureService featureService,
-        ThreadPool threadPool,
-        ActionFilters actionFilters,
-        IndexNameExpressionResolver indexNameExpressionResolver,
-        Consumer<List<DesiredNode>> desiredNodesValidator,
-        AllocationService allocationService
     ) {
         super(
             UpdateDesiredNodesAction.NAME,
@@ -99,7 +71,6 @@ public class TransportUpdateDesiredNodesAction extends TransportMasterNodeAction
         );
         this.rerouteService = rerouteService;
         this.featureService = featureService;
-        this.desiredNodesValidator = desiredNodesValidator;
         this.taskQueue = clusterService.createTaskQueue(
             "update-desired-nodes",
             Priority.URGENT,
@@ -119,10 +90,14 @@ public class TransportUpdateDesiredNodesAction extends TransportMasterNodeAction
         ClusterState state,
         ActionListener<UpdateDesiredNodesResponse> responseListener
     ) throws Exception {
-        ActionListener.run(responseListener, listener -> {
-            desiredNodesValidator.accept(request.getNodes());
-            taskQueue.submitTask("update-desired-nodes", new UpdateDesiredNodesTask(request, listener), request.masterNodeTimeout());
-        });
+        ActionListener.run(
+            responseListener,
+            listener -> taskQueue.submitTask(
+                "update-desired-nodes",
+                new UpdateDesiredNodesTask(request, listener),
+                request.masterNodeTimeout()
+            )
+        );
     }
 
     @Override

+ 0 - 74
server/src/main/java/org/elasticsearch/cluster/desirednodes/DesiredNodesSettingsValidator.java

@@ -1,74 +0,0 @@
-/*
- * 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.cluster.desirednodes;
-
-import org.elasticsearch.Build;
-import org.elasticsearch.Version;
-import org.elasticsearch.cluster.metadata.DesiredNode;
-import org.elasticsearch.core.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import static java.lang.String.format;
-
-public class DesiredNodesSettingsValidator implements Consumer<List<DesiredNode>> {
-    private record DesiredNodeValidationError(int position, @Nullable String externalId, RuntimeException exception) {}
-
-    @Override
-    public void accept(List<DesiredNode> nodes) {
-        final List<DesiredNodeValidationError> validationErrors = new ArrayList<>();
-        for (int i = 0; i < nodes.size(); i++) {
-            final DesiredNode node = nodes.get(i);
-            if (node.version().before(Version.CURRENT)) {
-                validationErrors.add(
-                    new DesiredNodeValidationError(
-                        i,
-                        node.externalId(),
-                        new IllegalArgumentException(
-                            format(
-                                Locale.ROOT,
-                                "Illegal node version [%s]. Only [%s] or newer versions are supported",
-                                node.version(),
-                                Build.current().version()
-                            )
-                        )
-                    )
-                );
-            }
-        }
-
-        if (validationErrors.isEmpty() == false) {
-            final String nodeIndicesWithFailures = validationErrors.stream()
-                .map(DesiredNodeValidationError::position)
-                .map(i -> Integer.toString(i))
-                .collect(Collectors.joining(","));
-
-            final String nodeIdsWithFailures = validationErrors.stream()
-                .map(DesiredNodeValidationError::externalId)
-                .collect(Collectors.joining(","));
-            IllegalArgumentException invalidSettingsException = new IllegalArgumentException(
-                format(
-                    Locale.ROOT,
-                    "Nodes with ids [%s] in positions [%s] contain invalid settings",
-                    nodeIdsWithFailures,
-                    nodeIndicesWithFailures
-                )
-            );
-            for (DesiredNodeValidationError exceptionTuple : validationErrors) {
-                invalidSettingsException.addSuppressed(exceptionTuple.exception);
-            }
-            throw invalidSettingsException;
-        }
-    }
-
-}

+ 69 - 24
server/src/main/java/org/elasticsearch/cluster/metadata/DesiredNode.java

@@ -13,6 +13,7 @@ import org.elasticsearch.TransportVersions;
 import org.elasticsearch.Version;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.cluster.node.DiscoveryNodeRole;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
@@ -20,6 +21,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.Processors;
 import org.elasticsearch.core.Nullable;
+import org.elasticsearch.core.UpdateForV9;
 import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.xcontent.ConstructingObjectParser;
 import org.elasticsearch.xcontent.ObjectParser;
@@ -35,6 +37,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 import static java.lang.String.format;
 import static org.elasticsearch.node.Node.NODE_EXTERNAL_ID_SETTING;
@@ -45,6 +48,7 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
 
     public static final NodeFeature RANGE_FLOAT_PROCESSORS_SUPPORTED = new NodeFeature("desired_node.range_float_processors");
     public static final NodeFeature DOUBLE_PROCESSORS_SUPPORTED = new NodeFeature("desired_node.double_processors");
+    public static final NodeFeature DESIRED_NODE_VERSION_DEPRECATED = new NodeFeature("desired_node.version_deprecated");
 
     public static final TransportVersion RANGE_FLOAT_PROCESSORS_SUPPORT_TRANSPORT_VERSION = TransportVersions.V_8_3_0;
 
@@ -53,6 +57,7 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
     private static final ParseField PROCESSORS_RANGE_FIELD = new ParseField("processors_range");
     private static final ParseField MEMORY_FIELD = new ParseField("memory");
     private static final ParseField STORAGE_FIELD = new ParseField("storage");
+    @UpdateForV9 // Remove deprecated field
     private static final ParseField VERSION_FIELD = new ParseField("node_version");
 
     public static final ConstructingObjectParser<DesiredNode, Void> PARSER = new ConstructingObjectParser<>(
@@ -64,7 +69,7 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
             (ProcessorsRange) args[2],
             (ByteSizeValue) args[3],
             (ByteSizeValue) args[4],
-            (Version) args[5]
+            (String) args[5]
         )
     );
 
@@ -99,49 +104,57 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
             ObjectParser.ValueType.STRING
         );
         parser.declareField(
-            ConstructingObjectParser.constructorArg(),
-            (p, c) -> parseVersion(p.text()),
+            ConstructingObjectParser.optionalConstructorArg(),
+            (p, c) -> p.text(),
             VERSION_FIELD,
             ObjectParser.ValueType.STRING
         );
     }
 
-    private static Version parseVersion(String version) {
-        if (version == null || version.isBlank()) {
-            throw new IllegalArgumentException(VERSION_FIELD.getPreferredName() + " must not be empty");
-        }
-        return Version.fromString(version);
-    }
-
     private final Settings settings;
     private final Processors processors;
     private final ProcessorsRange processorsRange;
     private final ByteSizeValue memory;
     private final ByteSizeValue storage;
-    private final Version version;
+
+    @UpdateForV9 // Remove deprecated version field
+    private final String version;
     private final String externalId;
     private final Set<DiscoveryNodeRole> roles;
 
-    public DesiredNode(Settings settings, ProcessorsRange processorsRange, ByteSizeValue memory, ByteSizeValue storage, Version version) {
+    @Deprecated
+    public DesiredNode(Settings settings, ProcessorsRange processorsRange, ByteSizeValue memory, ByteSizeValue storage, String version) {
         this(settings, null, processorsRange, memory, storage, version);
     }
 
-    public DesiredNode(Settings settings, double processors, ByteSizeValue memory, ByteSizeValue storage, Version version) {
+    @Deprecated
+    public DesiredNode(Settings settings, double processors, ByteSizeValue memory, ByteSizeValue storage, String version) {
         this(settings, Processors.of(processors), null, memory, storage, version);
     }
 
+    public DesiredNode(Settings settings, ProcessorsRange processorsRange, ByteSizeValue memory, ByteSizeValue storage) {
+        this(settings, null, processorsRange, memory, storage);
+    }
+
+    public DesiredNode(Settings settings, double processors, ByteSizeValue memory, ByteSizeValue storage) {
+        this(settings, Processors.of(processors), null, memory, storage);
+    }
+
+    DesiredNode(Settings settings, Processors processors, ProcessorsRange processorsRange, ByteSizeValue memory, ByteSizeValue storage) {
+        this(settings, processors, processorsRange, memory, storage, null);
+    }
+
     DesiredNode(
         Settings settings,
         Processors processors,
         ProcessorsRange processorsRange,
         ByteSizeValue memory,
         ByteSizeValue storage,
-        Version version
+        @Deprecated String version
     ) {
         assert settings != null;
         assert memory != null;
         assert storage != null;
-        assert version != null;
 
         if (processors == null && processorsRange == null) {
             throw new IllegalArgumentException(
@@ -190,10 +203,27 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
         }
         final var memory = ByteSizeValue.readFrom(in);
         final var storage = ByteSizeValue.readFrom(in);
-        final var version = Version.readVersion(in);
+        final String version;
+        if (in.getTransportVersion().onOrAfter(TransportVersions.DESIRED_NODE_VERSION_OPTIONAL_STRING)) {
+            version = in.readOptionalString();
+        } else {
+            version = Version.readVersion(in).toString();
+        }
         return new DesiredNode(settings, processors, processorsRange, memory, storage, version);
     }
 
+    private static final Pattern SEMANTIC_VERSION_PATTERN = Pattern.compile("^(\\d+\\.\\d+\\.\\d+)\\D?.*");
+
+    private static Version parseLegacyVersion(String version) {
+        if (version != null) {
+            var semanticVersionMatcher = SEMANTIC_VERSION_PATTERN.matcher(version);
+            if (semanticVersionMatcher.matches()) {
+                return Version.fromString(semanticVersionMatcher.group(1));
+            }
+        }
+        return null;
+    }
+
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         settings.writeTo(out);
@@ -207,7 +237,17 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
         }
         memory.writeTo(out);
         storage.writeTo(out);
-        Version.writeVersion(version, out);
+        if (out.getTransportVersion().onOrAfter(TransportVersions.DESIRED_NODE_VERSION_OPTIONAL_STRING)) {
+            out.writeOptionalString(version);
+        } else {
+            Version parsedVersion = parseLegacyVersion(version);
+            if (version == null) {
+                // Some node is from before we made the version field not required. If so, fill in with the current node version.
+                Version.writeVersion(Version.CURRENT, out);
+            } else {
+                Version.writeVersion(parsedVersion, out);
+            }
+        }
     }
 
     public static DesiredNode fromXContent(XContentParser parser) throws IOException {
@@ -234,7 +274,14 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
         }
         builder.field(MEMORY_FIELD.getPreferredName(), memory);
         builder.field(STORAGE_FIELD.getPreferredName(), storage);
-        builder.field(VERSION_FIELD.getPreferredName(), version);
+        addDeprecatedVersionField(builder);
+    }
+
+    @UpdateForV9 // Remove deprecated field from response
+    private void addDeprecatedVersionField(XContentBuilder builder) throws IOException {
+        if (version != null) {
+            builder.field(VERSION_FIELD.getPreferredName(), version);
+        }
     }
 
     public boolean hasMasterRole() {
@@ -292,10 +339,6 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
         return storage;
     }
 
-    public Version version() {
-        return version;
-    }
-
     public String externalId() {
         return externalId;
     }
@@ -356,8 +399,6 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
             + memory
             + ", storage="
             + storage
-            + ", version="
-            + version
             + ", externalId='"
             + externalId
             + '\''
@@ -366,6 +407,10 @@ public final class DesiredNode implements Writeable, ToXContentObject, Comparabl
             + '}';
     }
 
+    public boolean hasVersion() {
+        return Strings.isNullOrBlank(version) == false;
+    }
+
     public record ProcessorsRange(Processors min, @Nullable Processors max) implements Writeable, ToXContentObject {
 
         private static final ParseField MIN_FIELD = new ParseField("min");

+ 1 - 2
server/src/main/java/org/elasticsearch/cluster/metadata/DesiredNodeWithStatus.java

@@ -10,7 +10,6 @@ package org.elasticsearch.cluster.metadata;
 
 import org.elasticsearch.TransportVersion;
 import org.elasticsearch.TransportVersions;
-import org.elasticsearch.Version;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
@@ -45,7 +44,7 @@ public record DesiredNodeWithStatus(DesiredNode desiredNode, Status status)
                 (DesiredNode.ProcessorsRange) args[2],
                 (ByteSizeValue) args[3],
                 (ByteSizeValue) args[4],
-                (Version) args[5]
+                (String) args[5]
             ),
             // An unknown status is expected during upgrades to versions >= STATUS_TRACKING_SUPPORT_VERSION
             // the desired node status would be populated when a node in the newer version is elected as

+ 6 - 0
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataFeatures.java

@@ -13,6 +13,7 @@ import org.elasticsearch.features.FeatureSpecification;
 import org.elasticsearch.features.NodeFeature;
 
 import java.util.Map;
+import java.util.Set;
 
 public class MetadataFeatures implements FeatureSpecification {
     @Override
@@ -24,4 +25,9 @@ public class MetadataFeatures implements FeatureSpecification {
             Version.V_8_5_0
         );
     }
+
+    @Override
+    public Set<NodeFeature> getFeatures() {
+        return Set.of(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED);
+    }
 }

+ 25 - 0
server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestUpdateDesiredNodesAction.java

@@ -11,15 +11,30 @@ package org.elasticsearch.rest.action.admin.cluster;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesAction;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesRequest;
 import org.elasticsearch.client.internal.node.NodeClient;
+import org.elasticsearch.cluster.metadata.DesiredNode;
+import org.elasticsearch.common.logging.DeprecationLogger;
+import org.elasticsearch.features.NodeFeature;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.xcontent.XContentParseException;
 import org.elasticsearch.xcontent.XContentParser;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.function.Predicate;
 
 public class RestUpdateDesiredNodesAction extends BaseRestHandler {
+
+    private final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestUpdateDesiredNodesAction.class);
+    private static final String VERSION_DEPRECATION_MESSAGE =
+        "[version removal] Specifying node_version in desired nodes requests is deprecated.";
+    private final Predicate<NodeFeature> clusterSupportsFeature;
+
+    public RestUpdateDesiredNodesAction(Predicate<NodeFeature> clusterSupportsFeature) {
+        this.clusterSupportsFeature = clusterSupportsFeature;
+    }
+
     @Override
     public String getName() {
         return "update_desired_nodes";
@@ -41,6 +56,16 @@ public class RestUpdateDesiredNodesAction extends BaseRestHandler {
             updateDesiredNodesRequest = UpdateDesiredNodesRequest.fromXContent(historyId, version, dryRun, parser);
         }
 
+        if (clusterSupportsFeature.test(DesiredNode.DESIRED_NODE_VERSION_DEPRECATED)) {
+            if (updateDesiredNodesRequest.getNodes().stream().anyMatch(DesiredNode::hasVersion)) {
+                deprecationLogger.compatibleCritical("desired_nodes_version", VERSION_DEPRECATION_MESSAGE);
+            }
+        } else {
+            if (updateDesiredNodesRequest.getNodes().stream().anyMatch(n -> n.hasVersion() == false)) {
+                throw new XContentParseException("[node_version] field is required and must have a valid value");
+            }
+        }
+
         updateDesiredNodesRequest.masterNodeTimeout(request.paramAsTime("master_timeout", updateDesiredNodesRequest.masterNodeTimeout()));
         return restChannel -> client.execute(
             UpdateDesiredNodesAction.INSTANCE,

+ 0 - 2
server/src/test/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportUpdateDesiredNodesActionTests.java

@@ -56,7 +56,6 @@ public class TransportUpdateDesiredNodesActionTests extends DesiredNodesTestCase
             threadPool,
             mock(ActionFilters.class),
             mock(IndexNameExpressionResolver.class),
-            l -> {},
             mock(AllocationService.class)
         );
 
@@ -85,7 +84,6 @@ public class TransportUpdateDesiredNodesActionTests extends DesiredNodesTestCase
             threadPool,
             mock(ActionFilters.class),
             mock(IndexNameExpressionResolver.class),
-            l -> {},
             mock(AllocationService.class)
         );
 

+ 2 - 4
server/src/test/java/org/elasticsearch/action/admin/cluster/desirednodes/UpdateDesiredNodesRequestTests.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.action.admin.cluster.desirednodes;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.cluster.metadata.DesiredNode;
 import org.elasticsearch.common.settings.Settings;
@@ -46,14 +45,13 @@ public class UpdateDesiredNodesRequestTests extends ESTestCase {
             .build();
 
         if (randomBoolean()) {
-            return new DesiredNode(settings, randomFloat(), ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            return new DesiredNode(settings, randomFloat(), ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
         } else {
             return new DesiredNode(
                 settings,
                 new DesiredNode.ProcessorsRange(1, randomBoolean() ? null : (double) 1),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             );
         }
     }

+ 0 - 35
server/src/test/java/org/elasticsearch/cluster/desirednodes/DesiredNodesSettingsValidatorTests.java

@@ -1,35 +0,0 @@
-/*
- * 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.cluster.desirednodes;
-
-import org.elasticsearch.Version;
-import org.elasticsearch.cluster.metadata.DesiredNode;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.test.ESTestCase;
-
-import java.util.List;
-
-import static org.elasticsearch.cluster.metadata.DesiredNodesTestCase.randomDesiredNode;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.emptyArray;
-import static org.hamcrest.Matchers.not;
-
-public class DesiredNodesSettingsValidatorTests extends ESTestCase {
-    public void testNodeVersionValidation() {
-        final List<DesiredNode> desiredNodes = List.of(randomDesiredNode(Version.CURRENT.previousMajor(), Settings.EMPTY));
-
-        final DesiredNodesSettingsValidator validator = new DesiredNodesSettingsValidator();
-
-        final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> validator.accept(desiredNodes));
-        assertThat(exception.getMessage(), containsString("Nodes with ids"));
-        assertThat(exception.getMessage(), containsString("contain invalid settings"));
-        assertThat(exception.getSuppressed(), not(emptyArray()));
-        assertThat(exception.getSuppressed()[0].getMessage(), containsString("Illegal node version"));
-    }
-}

+ 6 - 19
server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodeSerializationTests.java

@@ -39,54 +39,41 @@ public class DesiredNodeSerializationTests extends AbstractXContentSerializingTe
     }
 
     public static DesiredNode mutateDesiredNode(DesiredNode instance) {
-        final var mutationBranch = randomInt(5);
+        final var mutationBranch = randomInt(4);
         return switch (mutationBranch) {
             case 0 -> new DesiredNode(
                 Settings.builder().put(instance.settings()).put(randomAlphaOfLength(10), randomInt()).build(),
                 instance.processors(),
                 instance.processorsRange(),
                 instance.memory(),
-                instance.storage(),
-                instance.version()
+                instance.storage()
             );
             case 1 -> new DesiredNode(
                 instance.settings(),
                 randomValueOtherThan(instance.processors(), () -> Processors.of(randomDouble() + randomIntBetween(1, 128))),
                 null,
                 instance.memory(),
-                instance.storage(),
-                instance.version()
+                instance.storage()
             );
             case 2 -> new DesiredNode(
                 instance.settings(),
                 randomValueOtherThan(instance.processorsRange(), DesiredNodesTestCase::randomProcessorRange),
                 instance.memory(),
-                instance.storage(),
-                instance.version()
+                instance.storage()
             );
             case 3 -> new DesiredNode(
                 instance.settings(),
                 instance.processors(),
                 instance.processorsRange(),
                 ByteSizeValue.ofGb(randomValueOtherThan(instance.memory().getGb(), () -> (long) randomIntBetween(1, 128))),
-                instance.storage(),
-                instance.version()
+                instance.storage()
             );
             case 4 -> new DesiredNode(
                 instance.settings(),
                 instance.processors(),
                 instance.processorsRange(),
                 instance.memory(),
-                ByteSizeValue.ofGb(randomValueOtherThan(instance.storage().getGb(), () -> (long) randomIntBetween(1, 128))),
-                instance.version()
-            );
-            case 5 -> new DesiredNode(
-                instance.settings(),
-                instance.processors(),
-                instance.processorsRange(),
-                instance.memory(),
-                instance.storage(),
-                instance.version().previousMajor()
+                ByteSizeValue.ofGb(randomValueOtherThan(instance.storage().getGb(), () -> (long) randomIntBetween(1, 128)))
             );
             default -> throw new IllegalStateException("Unexpected value: " + mutationBranch);
         };

+ 21 - 30
server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodeTests.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.cluster.metadata;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.cluster.node.DiscoveryNodeRole;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeValue;
@@ -38,7 +37,7 @@ public class DesiredNodeTests extends ESTestCase {
 
         final IllegalArgumentException exception = expectThrows(
             IllegalArgumentException.class,
-            () -> new DesiredNode(settings.build(), 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT)
+            () -> new DesiredNode(settings.build(), 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1))
         );
         assertThat(exception.getMessage(), is(equalTo("[node.name] or [node.external_id] is missing or empty")));
     }
@@ -47,7 +46,7 @@ public class DesiredNodeTests extends ESTestCase {
         final String nodeName = randomAlphaOfLength(10);
         final Settings settings = Settings.builder().put(NODE_NAME_SETTING.getKey(), nodeName).build();
 
-        DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+        DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
         assertThat(desiredNode.externalId(), is(notNullValue()));
         assertThat(desiredNode.externalId(), is(equalTo(nodeName)));
     }
@@ -57,7 +56,7 @@ public class DesiredNodeTests extends ESTestCase {
 
         expectThrows(
             IllegalArgumentException.class,
-            () -> new DesiredNode(settings, randomInvalidProcessor(), ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT)
+            () -> new DesiredNode(settings, randomInvalidProcessor(), ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1))
         );
 
         // Processor ranges
@@ -67,8 +66,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(randomInvalidProcessor(), randomFrom(random(), null, 1.0)),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             )
         );
         expectThrows(
@@ -77,8 +75,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(randomDouble() + 0.1, randomInvalidProcessor()),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             )
         );
         expectThrows(
@@ -87,8 +84,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(randomInvalidProcessor(), randomInvalidProcessor()),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             )
         );
 
@@ -100,8 +96,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(lowerBound, upperBound),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             )
         );
     }
@@ -110,7 +105,7 @@ public class DesiredNodeTests extends ESTestCase {
         {
             final Settings settings = Settings.builder().put(NODE_NAME_SETTING.getKey(), randomAlphaOfLength(10)).build();
 
-            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
             assertTrue(desiredNode.hasMasterRole());
         }
 
@@ -120,7 +115,7 @@ public class DesiredNodeTests extends ESTestCase {
                 .put(NODE_ROLES_SETTING.getKey(), "master")
                 .build();
 
-            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
             assertTrue(desiredNode.hasMasterRole());
         }
 
@@ -130,7 +125,7 @@ public class DesiredNodeTests extends ESTestCase {
                 .put(NODE_ROLES_SETTING.getKey(), "data_hot")
                 .build();
 
-            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            DesiredNode desiredNode = new DesiredNode(settings, 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
             assertFalse(desiredNode.hasMasterRole());
         }
     }
@@ -143,7 +138,7 @@ public class DesiredNodeTests extends ESTestCase {
             settings.put(NODE_ROLES_SETTING.getKey(), role.roleName());
         }
 
-        final var desiredNode = new DesiredNode(settings.build(), 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+        final var desiredNode = new DesiredNode(settings.build(), 1, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
 
         if (role != null) {
             assertThat(desiredNode.getRoles(), hasSize(1));
@@ -161,8 +156,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(0.4, 1.2),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             );
 
             assertThat(desiredNode.minProcessors().count(), is(equalTo(0.4)));
@@ -172,7 +166,7 @@ public class DesiredNodeTests extends ESTestCase {
         }
 
         {
-            final var desiredNode = new DesiredNode(settings, 1.2, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            final var desiredNode = new DesiredNode(settings, 1.2, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
 
             assertThat(desiredNode.minProcessors().count(), is(equalTo(1.2)));
             assertThat(desiredNode.roundedDownMinProcessors(), is(equalTo(1)));
@@ -181,7 +175,7 @@ public class DesiredNodeTests extends ESTestCase {
         }
 
         {
-            final var desiredNode = new DesiredNode(settings, 1024, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            final var desiredNode = new DesiredNode(settings, 1024, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
 
             assertThat(desiredNode.minProcessors().count(), is(equalTo(1024.0)));
             assertThat(desiredNode.roundedDownMinProcessors(), is(equalTo(1024)));
@@ -198,8 +192,7 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 new DesiredNode.ProcessorsRange(0.4, 1.2),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             );
             assertThat(desiredNode.clusterHasRequiredFeatures(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORTED::equals), is(true));
             assertThat(desiredNode.clusterHasRequiredFeatures(nf -> false), is(false));
@@ -210,15 +203,14 @@ public class DesiredNodeTests extends ESTestCase {
                 settings,
                 randomIntBetween(0, 10) + randomDoubleBetween(0.00001, 0.99999, true),
                 ByteSizeValue.ofGb(1),
-                ByteSizeValue.ofGb(1),
-                Version.CURRENT
+                ByteSizeValue.ofGb(1)
             );
             assertThat(desiredNode.clusterHasRequiredFeatures(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORTED::equals), is(true));
             assertThat(desiredNode.clusterHasRequiredFeatures(nf -> false), is(false));
         }
 
         {
-            final var desiredNode = new DesiredNode(settings, 2.0f, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1), Version.CURRENT);
+            final var desiredNode = new DesiredNode(settings, 2.0f, ByteSizeValue.ofGb(1), ByteSizeValue.ofGb(1));
             assertThat(desiredNode.clusterHasRequiredFeatures(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORTED::equals), is(true));
             assertThat(desiredNode.clusterHasRequiredFeatures(nf -> false), is(true));
         }
@@ -236,13 +228,12 @@ public class DesiredNodeTests extends ESTestCase {
         final DesiredNode desiredNode1;
         final DesiredNode desiredNode2;
         if (randomBoolean()) {
-            desiredNode1 = new DesiredNode(settings, processorCount, memory, storage, Version.CURRENT);
+            desiredNode1 = new DesiredNode(settings, processorCount, memory, storage);
             desiredNode2 = new DesiredNode(
                 settings,
                 isEqualOrCloseTo ? (float) processorCount : processorCount + maxDelta,
                 memory,
-                storage,
-                Version.CURRENT
+                storage
             );
         } else {
             final double desiredNodes1Min = processorCount;
@@ -268,8 +259,8 @@ public class DesiredNodeTests extends ESTestCase {
                 desiredNodes2Max
             );
 
-            desiredNode1 = new DesiredNode(settings, desiredNodes1ProcessorsRange, memory, storage, Version.CURRENT);
-            desiredNode2 = new DesiredNode(settings, desiredNodes2ProcessorsRange, memory, storage, Version.CURRENT);
+            desiredNode1 = new DesiredNode(settings, desiredNodes1ProcessorsRange, memory, storage);
+            desiredNode2 = new DesiredNode(settings, desiredNodes2ProcessorsRange, memory, storage);
         }
 
         assertThat(desiredNode1.equalsWithProcessorsCloseTo(desiredNode2), is(isEqualOrCloseTo));

+ 7 - 14
server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodesTestCase.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.cluster.metadata;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesRequest;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
@@ -44,38 +43,32 @@ public abstract class DesiredNodesTestCase extends ESTestCase {
     }
 
     public static DesiredNode randomDesiredNode() {
-        return randomDesiredNode(Version.CURRENT, Settings.EMPTY);
+        return randomDesiredNode(Settings.EMPTY);
     }
 
     public static DesiredNode randomDesiredNode(Settings settings) {
-        return randomDesiredNode(Version.CURRENT, settings);
-    }
-
-    public static DesiredNode randomDesiredNode(Version version, Settings settings) {
         if (randomBoolean()) {
-            return randomDesiredNode(version, settings, randomProcessorRange());
+            return randomDesiredNode(settings, randomProcessorRange());
         } else {
-            return randomDesiredNode(version, settings, randomNumberOfProcessors());
+            return randomDesiredNode(settings, randomNumberOfProcessors());
         }
     }
 
-    public static DesiredNode randomDesiredNode(Version version, Settings settings, double processors) {
+    public static DesiredNode randomDesiredNode(Settings settings, double processors) {
         return new DesiredNode(
             addExternalIdIfMissing(settings),
             processors,
             ByteSizeValue.ofGb(randomIntBetween(1, 1024)),
-            ByteSizeValue.ofTb(randomIntBetween(1, 40)),
-            version
+            ByteSizeValue.ofTb(randomIntBetween(1, 40))
         );
     }
 
-    public static DesiredNode randomDesiredNode(Version version, Settings settings, DesiredNode.ProcessorsRange processorsRange) {
+    public static DesiredNode randomDesiredNode(Settings settings, DesiredNode.ProcessorsRange processorsRange) {
         return new DesiredNode(
             addExternalIdIfMissing(settings),
             processorsRange,
             ByteSizeValue.ofGb(randomIntBetween(1, 1024)),
-            ByteSizeValue.ofTb(randomIntBetween(1, 40)),
-            version
+            ByteSizeValue.ofTb(randomIntBetween(1, 40))
         );
     }
 

+ 1 - 3
server/src/test/java/org/elasticsearch/cluster/metadata/DesiredNodesTests.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.cluster.metadata;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.common.UUIDs;
@@ -189,8 +188,7 @@ public class DesiredNodesTests extends DesiredNodesTestCase {
             desiredNode.settings(),
             desiredNode.minProcessors().count() + randomIntBetween(1, 10),
             ByteSizeValue.ofGb(desiredNode.memory().getGb() + randomIntBetween(15, 20)),
-            ByteSizeValue.ofGb(desiredNode.storage().getGb() + randomIntBetween(1, 100)),
-            Version.CURRENT
+            ByteSizeValue.ofGb(desiredNode.storage().getGb() + randomIntBetween(1, 100))
         );
     }
 }

+ 1 - 2
x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDeciderIT.java

@@ -7,7 +7,6 @@
 
 package org.elasticsearch.xpack.cluster.routing.allocation;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesAction;
 import org.elasticsearch.action.admin.cluster.desirednodes.UpdateDesiredNodesRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
@@ -506,7 +505,7 @@ public class DataTierAllocationDeciderIT extends ESIntegTestCase {
             .put(NODE_EXTERNAL_ID_SETTING.getKey(), externalId)
             .put(NODE_NAME_SETTING.getKey(), externalId)
             .build();
-        return new DesiredNode(settings, 1, ByteSizeValue.ONE, ByteSizeValue.ONE, Version.CURRENT);
+        return new DesiredNode(settings, 1, ByteSizeValue.ONE, ByteSizeValue.ONE);
     }
 
     private void updateDesiredNodes(DesiredNode... desiredNodes) {

+ 1 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/cluster/routing/allocation/DataTierAllocationDeciderTests.java

@@ -9,7 +9,6 @@ package org.elasticsearch.xpack.cluster.routing.allocation;
 
 import joptsimple.internal.Strings;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.ESAllocationTestCase;
@@ -896,8 +895,7 @@ public class DataTierAllocationDeciderTests extends ESAllocationTestCase {
                 .build(),
             1,
             ByteSizeValue.ONE,
-            ByteSizeValue.ONE,
-            Version.CURRENT
+            ByteSizeValue.ONE
         );
     }