Просмотр исходного кода

[8.x] Add min. read-only index version compatible to DiscoveryNode (#118744) (#118884)

* [8.x] Add min. read-only index version compatible to DiscoveryNode (#118744)

In a short future we'd like to use this information in methods like IndexMetadataVerifier#checkSupportedVersion and NodeJoineExecutor to allow opening indices in N-2 versions as read-only indices on ES V9.

* MINIMUM_READONLY_COMPATIBLE
Tanguy Leroux 10 месяцев назад
Родитель
Сommit
0784601d28

+ 2 - 0
docs/reference/indices/shard-stores.asciidoc

@@ -173,6 +173,7 @@ The API returns the following response:
                             "roles": [...],
                             "version": "8.10.0",
                             "min_index_version": 7000099,
+                            "min_read_only_index_version": 7000099,
                             "max_index_version": 8100099
                         },
                         "allocation_id": "2iNySv_OQVePRX-yaRH_lQ", <4>
@@ -193,6 +194,7 @@ The API returns the following response:
 // TESTRESPONSE[s/"roles": \[[^]]*\]/"roles": $body.$_path/]
 // TESTRESPONSE[s/"8.10.0"/\$node_version/]
 // TESTRESPONSE[s/"min_index_version": 7000099/"min_index_version": $body.$_path/]
+// TESTRESPONSE[s/"min_index_version": 7000099/"min_index_version": $body.$_path/]
 // TESTRESPONSE[s/"max_index_version": 8100099/"max_index_version": $body.$_path/]
 
 

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

@@ -147,6 +147,7 @@ public class TransportVersions {
     public static final TransportVersion SEMANTIC_QUERY_LENIENT = def(8_807_00_0);
     public static final TransportVersion ESQL_QUERY_BUILDER_IN_SEARCH_FUNCTIONS = def(8_808_00_0);
     public static final TransportVersion EQL_ALLOW_PARTIAL_SEARCH_RESULTS = def(8_809_00_0);
+    public static final TransportVersion NODE_VERSION_INFORMATION_WITH_MIN_READ_ONLY_INDEX_VERSION = def(8_810_00_0);
 
     /*
      * STOP! READ THIS FIRST! No, really,

+ 19 - 1
server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNode.java

@@ -38,6 +38,7 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
+import static org.elasticsearch.TransportVersions.NODE_VERSION_INFORMATION_WITH_MIN_READ_ONLY_INDEX_VERSION;
 import static org.elasticsearch.node.NodeRoleSettings.NODE_ROLES_SETTING;
 
 /**
@@ -339,7 +340,16 @@ public class DiscoveryNode implements Writeable, ToXContentFragment {
         }
         this.roles = Collections.unmodifiableSortedSet(roles);
         if (in.getTransportVersion().onOrAfter(TransportVersions.V_8_10_X)) {
-            versionInfo = new VersionInformation(Version.readVersion(in), IndexVersion.readVersion(in), IndexVersion.readVersion(in));
+            Version version = Version.readVersion(in);
+            IndexVersion minIndexVersion = IndexVersion.readVersion(in);
+            IndexVersion minReadOnlyIndexVersion;
+            if (in.getTransportVersion().onOrAfter(NODE_VERSION_INFORMATION_WITH_MIN_READ_ONLY_INDEX_VERSION)) {
+                minReadOnlyIndexVersion = IndexVersion.readVersion(in);
+            } else {
+                minReadOnlyIndexVersion = minIndexVersion;
+            }
+            IndexVersion maxIndexVersion = IndexVersion.readVersion(in);
+            versionInfo = new VersionInformation(version, minIndexVersion, minReadOnlyIndexVersion, maxIndexVersion);
         } else {
             versionInfo = inferVersionInformation(Version.readVersion(in));
         }
@@ -378,6 +388,9 @@ public class DiscoveryNode implements Writeable, ToXContentFragment {
         if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_10_X)) {
             Version.writeVersion(versionInfo.nodeVersion(), out);
             IndexVersion.writeVersion(versionInfo.minIndexVersion(), out);
+            if (out.getTransportVersion().onOrAfter(NODE_VERSION_INFORMATION_WITH_MIN_READ_ONLY_INDEX_VERSION)) {
+                IndexVersion.writeVersion(versionInfo.minReadOnlyIndexVersion(), out);
+            }
             IndexVersion.writeVersion(versionInfo.maxIndexVersion(), out);
         } else {
             Version.writeVersion(versionInfo.nodeVersion(), out);
@@ -504,6 +517,10 @@ public class DiscoveryNode implements Writeable, ToXContentFragment {
         return versionInfo.minIndexVersion();
     }
 
+    public IndexVersion getMinReadOnlyIndexVersion() {
+        return versionInfo.minReadOnlyIndexVersion();
+    }
+
     public IndexVersion getMaxIndexVersion() {
         return versionInfo.maxIndexVersion();
     }
@@ -603,6 +620,7 @@ public class DiscoveryNode implements Writeable, ToXContentFragment {
         builder.endArray();
         builder.field("version", versionInfo.nodeVersion());
         builder.field("min_index_version", versionInfo.minIndexVersion());
+        builder.field("min_read_only_index_version", versionInfo.minReadOnlyIndexVersion());
         builder.field("max_index_version", versionInfo.maxIndexVersion());
         builder.endObject();
         return builder;

+ 15 - 0
server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java

@@ -70,6 +70,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
     private final Version minNodeVersion;
     private final IndexVersion maxDataNodeCompatibleIndexVersion;
     private final IndexVersion minSupportedIndexVersion;
+    private final IndexVersion minReadOnlySupportedIndexVersion;
 
     private final Map<String, Set<String>> tiersToNodeIds;
 
@@ -86,6 +87,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
         Version minNodeVersion,
         IndexVersion maxDataNodeCompatibleIndexVersion,
         IndexVersion minSupportedIndexVersion,
+        IndexVersion minReadOnlySupportedIndexVersion,
         Map<String, Set<String>> tiersToNodeIds
     ) {
         this.nodeLeftGeneration = nodeLeftGeneration;
@@ -103,6 +105,8 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
         this.maxNodeVersion = maxNodeVersion;
         this.maxDataNodeCompatibleIndexVersion = maxDataNodeCompatibleIndexVersion;
         this.minSupportedIndexVersion = minSupportedIndexVersion;
+        this.minReadOnlySupportedIndexVersion = minReadOnlySupportedIndexVersion;
+        assert minReadOnlySupportedIndexVersion.onOrBefore(minSupportedIndexVersion);
         assert (localNodeId == null) == (localNode == null);
         this.tiersToNodeIds = tiersToNodeIds;
     }
@@ -122,6 +126,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
             minNodeVersion,
             maxDataNodeCompatibleIndexVersion,
             minSupportedIndexVersion,
+            minReadOnlySupportedIndexVersion,
             tiersToNodeIds
         );
     }
@@ -382,6 +387,13 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
         return minSupportedIndexVersion;
     }
 
+    /**
+     * Returns the minimum index version for read-only indices supported by all nodes in the cluster
+     */
+    public IndexVersion getMinReadOnlySupportedIndexVersion() {
+        return minReadOnlySupportedIndexVersion;
+    }
+
     /**
      * Return the node-left generation, which is the number of times the cluster membership has been updated by removing one or more nodes.
      * <p>
@@ -841,6 +853,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
             Version minNonClientNodeVersion = null;
             IndexVersion maxDataNodeCompatibleIndexVersion = null;
             IndexVersion minSupportedIndexVersion = null;
+            IndexVersion minReadOnlySupportedIndexVersion = null;
             for (Map.Entry<String, DiscoveryNode> nodeEntry : nodes.entrySet()) {
                 DiscoveryNode discoNode = nodeEntry.getValue();
                 Version version = discoNode.getVersion();
@@ -851,6 +864,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
                 minNodeVersion = min(minNodeVersion, version);
                 maxNodeVersion = max(maxNodeVersion, version);
                 minSupportedIndexVersion = max(minSupportedIndexVersion, discoNode.getMinIndexVersion());
+                minReadOnlySupportedIndexVersion = max(minReadOnlySupportedIndexVersion, discoNode.getMinReadOnlyIndexVersion());
             }
 
             final long newNodeLeftGeneration;
@@ -884,6 +898,7 @@ public class DiscoveryNodes implements Iterable<DiscoveryNode>, SimpleDiffable<D
                 Objects.requireNonNullElse(minNodeVersion, Version.CURRENT.minimumCompatibilityVersion()),
                 Objects.requireNonNullElse(maxDataNodeCompatibleIndexVersion, IndexVersion.current()),
                 Objects.requireNonNullElse(minSupportedIndexVersion, IndexVersions.MINIMUM_COMPATIBLE),
+                Objects.requireNonNullElse(minReadOnlySupportedIndexVersion, IndexVersions.MINIMUM_READONLY_COMPATIBLE),
                 computeTiersToNodesMap(dataNodes)
             );
         }

+ 18 - 4
server/src/main/java/org/elasticsearch/cluster/node/VersionInformation.java

@@ -17,15 +17,22 @@ import java.util.Objects;
 
 /**
  * Represents the versions of various aspects of an Elasticsearch node.
- * @param nodeVersion       The node {@link Version}
- * @param minIndexVersion   The minimum {@link IndexVersion} supported by this node
- * @param maxIndexVersion   The maximum {@link IndexVersion} supported by this node
+ * @param nodeVersion               The node {@link Version}
+ * @param minIndexVersion           The minimum {@link IndexVersion} supported by this node
+ * @param minReadOnlyIndexVersion   The minimum {@link IndexVersion} for read-only indices supported by this node
+ * @param maxIndexVersion           The maximum {@link IndexVersion} supported by this node
  */
-public record VersionInformation(Version nodeVersion, IndexVersion minIndexVersion, IndexVersion maxIndexVersion) {
+public record VersionInformation(
+    Version nodeVersion,
+    IndexVersion minIndexVersion,
+    IndexVersion minReadOnlyIndexVersion,
+    IndexVersion maxIndexVersion
+) {
 
     public static final VersionInformation CURRENT = new VersionInformation(
         Version.CURRENT,
         IndexVersions.MINIMUM_COMPATIBLE,
+        IndexVersions.MINIMUM_READONLY_COMPATIBLE,
         IndexVersion.current()
     );
 
@@ -45,9 +52,16 @@ public record VersionInformation(Version nodeVersion, IndexVersion minIndexVersi
         }
     }
 
+    @Deprecated
+    public VersionInformation(Version version, IndexVersion minIndexVersion, IndexVersion maxIndexVersion) {
+        this(version, minIndexVersion, minIndexVersion, maxIndexVersion);
+    }
+
     public VersionInformation {
         Objects.requireNonNull(nodeVersion);
         Objects.requireNonNull(minIndexVersion);
+        Objects.requireNonNull(minReadOnlyIndexVersion);
         Objects.requireNonNull(maxIndexVersion);
+        assert minReadOnlyIndexVersion.onOrBefore(minIndexVersion) : minReadOnlyIndexVersion + " > " + minIndexVersion;
     }
 }

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

@@ -110,6 +110,7 @@ public class HandshakingTransportAddressConnector implements TransportAddressCon
                     new VersionInformation(
                         Version.CURRENT.minimumCompatibilityVersion(),
                         IndexVersions.MINIMUM_COMPATIBLE,
+                        IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                         IndexVersion.current()
                     )
                 ),

+ 2 - 1
server/src/main/java/org/elasticsearch/index/IndexVersions.java

@@ -178,6 +178,7 @@ public class IndexVersions {
      */
 
     public static final IndexVersion MINIMUM_COMPATIBLE = V_7_0_0;
+    public static final IndexVersion MINIMUM_READONLY_COMPATIBLE = MINIMUM_COMPATIBLE;
 
     static final NavigableMap<Integer, IndexVersion> VERSION_IDS = getAllVersionIds(IndexVersions.class);
     static final IndexVersion LATEST_DEFINED;
@@ -193,7 +194,7 @@ public class IndexVersions {
         Map<Integer, String> versionIdFields = new HashMap<>();
         NavigableMap<Integer, IndexVersion> builder = new TreeMap<>();
 
-        Set<String> ignore = Set.of("ZERO", "MINIMUM_COMPATIBLE");
+        Set<String> ignore = Set.of("ZERO", "MINIMUM_COMPATIBLE", "MINIMUM_READONLY_COMPATIBLE");
 
         for (Field declaredField : cls.getFields()) {
             if (declaredField.getType().equals(IndexVersion.class)) {

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

@@ -304,6 +304,7 @@ public class ProxyConnectionStrategy extends RemoteConnectionStrategy {
                     new VersionInformation(
                         Version.CURRENT.minimumCompatibilityVersion(),
                         IndexVersions.MINIMUM_COMPATIBLE,
+                        IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                         IndexVersion.current()
                     )
                 );

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

@@ -505,6 +505,7 @@ public class SniffConnectionStrategy extends RemoteConnectionStrategy {
         var seedVersion = new VersionInformation(
             Version.CURRENT.minimumCompatibilityVersion(),
             IndexVersions.MINIMUM_COMPATIBLE,
+            IndexVersions.MINIMUM_READONLY_COMPATIBLE,
             IndexVersion.current()
         );
         if (proxyAddress == null || proxyAddress.isEmpty()) {

+ 2 - 0
server/src/test/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteResponseTests.java

@@ -128,6 +128,7 @@ public class ClusterRerouteResponseTests extends ESTestCase {
                             ],
                             "version": "%s",
                             "min_index_version": %s,
+                            "min_read_only_index_version": %s,
                             "max_index_version": %s
                           }
                         },
@@ -219,6 +220,7 @@ public class ClusterRerouteResponseTests extends ESTestCase {
                 clusterState.getNodes().get("node0").getEphemeralId(),
                 Version.CURRENT,
                 IndexVersions.MINIMUM_COMPATIBLE,
+                IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                 IndexVersion.current(),
                 IndexVersion.current(),
                 IndexVersion.current()

+ 6 - 0
server/src/test/java/org/elasticsearch/cluster/ClusterStateTests.java

@@ -213,6 +213,7 @@ public class ClusterStateTests extends ESTestCase {
                               ],
                               "version": "%s",
                               "min_index_version":%s,
+                              "min_read_only_index_version":%s,
                               "max_index_version":%s
                             }
                           },
@@ -389,6 +390,7 @@ public class ClusterStateTests extends ESTestCase {
                     ephemeralId,
                     Version.CURRENT,
                     IndexVersions.MINIMUM_COMPATIBLE,
+                    IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                     IndexVersion.current(),
                     TransportVersion.current(),
                     IndexVersion.current(),
@@ -488,6 +490,7 @@ public class ClusterStateTests extends ESTestCase {
                           ],
                           "version" : "%s",
                           "min_index_version" : %s,
+                          "min_read_only_index_version" : %s,
                           "max_index_version" : %s
                         }
                       },
@@ -663,6 +666,7 @@ public class ClusterStateTests extends ESTestCase {
                 ephemeralId,
                 Version.CURRENT,
                 IndexVersions.MINIMUM_COMPATIBLE,
+                IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                 IndexVersion.current(),
                 TransportVersion.current(),
                 IndexVersion.current(),
@@ -762,6 +766,7 @@ public class ClusterStateTests extends ESTestCase {
                           ],
                           "version" : "%s",
                           "min_index_version" : %s,
+                          "min_read_only_index_version" : %s,
                           "max_index_version" : %s
                         }
                       },
@@ -943,6 +948,7 @@ public class ClusterStateTests extends ESTestCase {
                 ephemeralId,
                 Version.CURRENT,
                 IndexVersions.MINIMUM_COMPATIBLE,
+                IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                 IndexVersion.current(),
                 TransportVersion.current(),
                 IndexVersion.current(),

+ 61 - 0
server/src/test/java/org/elasticsearch/cluster/node/DiscoveryNodeTests.java

@@ -31,6 +31,8 @@ import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 import static org.elasticsearch.test.NodeRoles.nonRemoteClusterClientNode;
 import static org.elasticsearch.test.NodeRoles.remoteClusterClientNode;
+import static org.elasticsearch.test.TransportVersionUtils.getPreviousVersion;
+import static org.elasticsearch.test.TransportVersionUtils.randomVersionBetween;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
@@ -221,6 +223,7 @@ public class DiscoveryNodeTests extends ESTestCase {
                             ],
                             "version" : "%s",
                             "min_index_version" : %s,
+                            "min_read_only_index_version" : %s,
                             "max_index_version" : %s
                           }
                         }""",
@@ -228,6 +231,7 @@ public class DiscoveryNodeTests extends ESTestCase {
                     withExternalId ? "test-external-id" : "test-name",
                     Version.CURRENT,
                     IndexVersions.MINIMUM_COMPATIBLE,
+                    IndexVersions.MINIMUM_READONLY_COMPATIBLE,
                     IndexVersion.current()
                 )
             )
@@ -250,4 +254,61 @@ public class DiscoveryNodeTests extends ESTestCase {
         assertThat(toString, containsString("{" + node.getVersion() + "}"));
         assertThat(toString, containsString("{test-attr=val}"));// attributes
     }
+
+    public void testDiscoveryNodeMinReadOnlyVersionSerialization() throws Exception {
+        var node = DiscoveryNodeUtils.create("_id", buildNewFakeTransportAddress(), Version.CURRENT);
+
+        {
+            try (var out = new BytesStreamOutput()) {
+                out.setTransportVersion(TransportVersion.current());
+                node.writeTo(out);
+
+                try (var in = StreamInput.wrap(out.bytes().array())) {
+                    in.setTransportVersion(TransportVersion.current());
+
+                    var deserialized = new DiscoveryNode(in);
+                    assertThat(deserialized.getId(), equalTo(node.getId()));
+                    assertThat(deserialized.getAddress(), equalTo(node.getAddress()));
+                    assertThat(deserialized.getMinIndexVersion(), equalTo(node.getMinIndexVersion()));
+                    assertThat(deserialized.getMaxIndexVersion(), equalTo(node.getMaxIndexVersion()));
+                    assertThat(deserialized.getMinReadOnlyIndexVersion(), equalTo(node.getMinReadOnlyIndexVersion()));
+                    assertThat(deserialized.getVersionInformation(), equalTo(node.getVersionInformation()));
+                }
+            }
+        }
+
+        {
+            var oldVersion = randomVersionBetween(
+                random(),
+                TransportVersions.MINIMUM_COMPATIBLE,
+                getPreviousVersion(TransportVersions.NODE_VERSION_INFORMATION_WITH_MIN_READ_ONLY_INDEX_VERSION)
+            );
+            try (var out = new BytesStreamOutput()) {
+                out.setTransportVersion(oldVersion);
+                node.writeTo(out);
+
+                try (var in = StreamInput.wrap(out.bytes().array())) {
+                    in.setTransportVersion(oldVersion);
+
+                    var deserialized = new DiscoveryNode(in);
+                    assertThat(deserialized.getId(), equalTo(node.getId()));
+                    assertThat(deserialized.getAddress(), equalTo(node.getAddress()));
+                    assertThat(deserialized.getMinIndexVersion(), equalTo(node.getMinIndexVersion()));
+                    assertThat(deserialized.getMaxIndexVersion(), equalTo(node.getMaxIndexVersion()));
+                    assertThat(deserialized.getMinReadOnlyIndexVersion(), equalTo(node.getMinIndexVersion()));
+                    assertThat(
+                        deserialized.getVersionInformation(),
+                        equalTo(
+                            new VersionInformation(
+                                node.getVersion(),
+                                node.getMinIndexVersion(),
+                                node.getMinIndexVersion(),
+                                node.getMaxIndexVersion()
+                            )
+                        )
+                    );
+                }
+            }
+        }
+    }
 }

+ 5 - 2
test/framework/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodeUtils.java

@@ -69,6 +69,7 @@ public class DiscoveryNodeUtils {
         private Set<DiscoveryNodeRole> roles = DiscoveryNodeRole.roles();
         private Version version;
         private IndexVersion minIndexVersion;
+        private IndexVersion minReadOnlyIndexVersion;
         private IndexVersion maxIndexVersion;
         private String externalId;
 
@@ -115,6 +116,7 @@ public class DiscoveryNodeUtils {
         public Builder version(Version version, IndexVersion minIndexVersion, IndexVersion maxIndexVersion) {
             this.version = version;
             this.minIndexVersion = minIndexVersion;
+            this.minReadOnlyIndexVersion = minIndexVersion;
             this.maxIndexVersion = maxIndexVersion;
             return this;
         }
@@ -122,6 +124,7 @@ public class DiscoveryNodeUtils {
         public Builder version(VersionInformation versions) {
             this.version = versions.nodeVersion();
             this.minIndexVersion = versions.minIndexVersion();
+            this.minReadOnlyIndexVersion = versions.minReadOnlyIndexVersion();
             this.maxIndexVersion = versions.maxIndexVersion();
             return this;
         }
@@ -149,10 +152,10 @@ public class DiscoveryNodeUtils {
             }
 
             VersionInformation versionInfo;
-            if (minIndexVersion == null || maxIndexVersion == null) {
+            if (minIndexVersion == null || minReadOnlyIndexVersion == null || maxIndexVersion == null) {
                 versionInfo = VersionInformation.inferVersions(version);
             } else {
-                versionInfo = new VersionInformation(version, minIndexVersion, maxIndexVersion);
+                versionInfo = new VersionInformation(version, minIndexVersion, minReadOnlyIndexVersion, maxIndexVersion);
             }
 
             return new DiscoveryNode(name, id, ephemeralId, hostName, hostAddress, address, attributes, roles, versionInfo, externalId);

+ 2 - 0
x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java

@@ -462,6 +462,7 @@ public class ClusterStatsMonitoringDocTests extends BaseMonitoringDocTestCase<Cl
             pluginEsBuildVersion,
             Version.CURRENT,
             IndexVersions.MINIMUM_COMPATIBLE,
+            IndexVersions.MINIMUM_READONLY_COMPATIBLE,
             IndexVersion.current(),
             apmIndicesExist };
         final String expectedJson = """
@@ -817,6 +818,7 @@ public class ClusterStatsMonitoringDocTests extends BaseMonitoringDocTestCase<Cl
                     ],
                     "version": "%s",
                     "min_index_version":%s,
+                    "min_read_only_index_version":%s,
                     "max_index_version":%s
                   }
                 },