Skip to content

Commit 7a4f1b6

Browse files
authored
[7.x] Add DiscoveryNodeRole compatibility role for bwc tier serialization (#63581) (#63612)
When introducing new roles, older versions of Elasticsearch aren't aware of these roles. This can lead to a situation where an old (say 7.9.x) master node sees nodes in the cluster of version 7.10+ with "data_hot" or "data_content" roles, and thinks those roles are not eligible to hold data (since the older node has no concept of these roles). This adds a method to `DiscoveryNodeRole` where a role can return a compatibility role to be used for serializing to an older Elasicsearch version. The new formalized data tier roles (`data_content`, `data_hot`, `data_warm`, `data_cold`) uses this mechanism to serialize as regular "data" roles when talking to an older Elasticsearch node. This will allow these nodes to still contain data during a rolling upgrade where the master is upgraded last. Relates to #60848
1 parent dff4f98 commit 7a4f1b6

File tree

6 files changed

+167
-2
lines changed

6 files changed

+167
-2
lines changed

server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNode.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,9 @@ public void writeTo(StreamOutput out) throws IOException {
325325
if (out.getVersion().onOrAfter(Version.V_7_3_0)) {
326326
out.writeVInt(roles.size());
327327
for (final DiscoveryNodeRole role : roles) {
328-
out.writeString(role.roleName());
329-
out.writeString(role.roleNameAbbreviation());
328+
final DiscoveryNodeRole compatibleRole = role.getCompatibilityRole(out.getVersion());
329+
out.writeString(compatibleRole.roleName());
330+
out.writeString(compatibleRole.roleNameAbbreviation());
330331
}
331332
} else {
332333
// an old node will only understand legacy roles since pluggable roles is a new concept

server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodeRole.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ public boolean canContainData() {
9191
return false;
9292
}
9393

94+
/**
95+
* When serializing a {@link DiscoveryNodeRole}, the role may not be available to nodes of
96+
* previous versions, where the role had not yet been added. This method allows overriding
97+
* the role that should be serialized when communicating to versions prior to the introduction
98+
* of the discovery node role.
99+
*/
100+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
101+
return this;
102+
}
103+
94104
@Override
95105
public final boolean equals(Object o) {
96106
if (this == o) return true;

server/src/test/java/org/elasticsearch/cluster/node/DiscoveryNodeTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
import org.elasticsearch.Version;
2323
import org.elasticsearch.common.io.stream.BytesStreamOutput;
2424
import org.elasticsearch.common.io.stream.StreamInput;
25+
import org.elasticsearch.common.settings.Setting;
2526
import org.elasticsearch.common.settings.Settings;
2627
import org.elasticsearch.common.transport.TransportAddress;
2728
import org.elasticsearch.test.ESTestCase;
2829

2930
import java.net.InetAddress;
31+
import java.util.Collections;
3032
import java.util.HashSet;
3133
import java.util.Set;
34+
import java.util.stream.Collectors;
3235

3336
import static java.util.Collections.emptyMap;
3437
import static java.util.Collections.emptySet;
@@ -88,6 +91,53 @@ public void testDiscoveryNodeSerializationKeepsHost() throws Exception {
8891
assertEquals(transportAddress.getPort(), serialized.getAddress().getPort());
8992
}
9093

94+
public void testDiscoveryNodeRoleWithOldVersion() throws Exception {
95+
InetAddress inetAddress = InetAddress.getByAddress("name1", new byte[] { (byte) 192, (byte) 168, (byte) 0, (byte) 1});
96+
TransportAddress transportAddress = new TransportAddress(inetAddress, randomIntBetween(0, 65535));
97+
98+
DiscoveryNodeRole customRole = new DiscoveryNodeRole("custom_role", "z") {
99+
@Override
100+
public Setting<Boolean> legacySetting() {
101+
return null;
102+
}
103+
104+
@Override
105+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
106+
if (nodeVersion.equals(Version.CURRENT)) {
107+
return this;
108+
} else {
109+
return DiscoveryNodeRole.DATA_ROLE;
110+
}
111+
}
112+
};
113+
114+
DiscoveryNode node = new DiscoveryNode("name1", "id1", transportAddress, emptyMap(),
115+
Collections.singleton(customRole), Version.CURRENT);
116+
117+
{
118+
BytesStreamOutput streamOutput = new BytesStreamOutput();
119+
streamOutput.setVersion(Version.CURRENT);
120+
node.writeTo(streamOutput);
121+
122+
StreamInput in = StreamInput.wrap(streamOutput.bytes().toBytesRef().bytes);
123+
DiscoveryNode serialized = new DiscoveryNode(in);
124+
assertThat(serialized.getRoles().stream().map(DiscoveryNodeRole::roleName).collect(Collectors.joining()),
125+
equalTo("custom_role"));
126+
}
127+
128+
{
129+
BytesStreamOutput streamOutput = new BytesStreamOutput();
130+
streamOutput.setVersion(Version.V_7_10_0);
131+
node.writeTo(streamOutput);
132+
133+
StreamInput in = StreamInput.wrap(streamOutput.bytes().toBytesRef().bytes);
134+
DiscoveryNode serialized = new DiscoveryNode(in);
135+
assertThat(serialized.getRoles().stream().map(DiscoveryNodeRole::roleName).collect(Collectors.joining()),
136+
equalTo("data"));
137+
}
138+
139+
}
140+
91141
public void testDiscoveryNodeIsRemoteClusterClientDefault() {
92142
runTestDiscoveryNodeIsRemoteClusterClient(Settings.EMPTY, true);
93143
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/DataTier.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import org.apache.logging.log4j.LogManager;
1010
import org.apache.logging.log4j.Logger;
11+
import org.elasticsearch.Version;
1112
import org.elasticsearch.cluster.metadata.IndexMetadata;
1213
import org.elasticsearch.cluster.node.DiscoveryNode;
1314
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
@@ -80,6 +81,11 @@ public Setting<Boolean> legacySetting() {
8081
public boolean canContainData() {
8182
return true;
8283
}
84+
85+
@Override
86+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
87+
return nodeVersion.before(Version.V_7_10_0) ? DiscoveryNodeRole.DATA_ROLE : this;
88+
}
8389
};
8490

8591
public static DiscoveryNodeRole DATA_HOT_NODE_ROLE = new DiscoveryNodeRole("data_hot", "h") {
@@ -97,6 +103,11 @@ public Setting<Boolean> legacySetting() {
97103
public boolean canContainData() {
98104
return true;
99105
}
106+
107+
@Override
108+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
109+
return nodeVersion.before(Version.V_7_10_0) ? DiscoveryNodeRole.DATA_ROLE : this;
110+
}
100111
};
101112

102113
public static DiscoveryNodeRole DATA_WARM_NODE_ROLE = new DiscoveryNodeRole("data_warm", "w") {
@@ -114,6 +125,11 @@ public Setting<Boolean> legacySetting() {
114125
public boolean canContainData() {
115126
return true;
116127
}
128+
129+
@Override
130+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
131+
return nodeVersion.before(Version.V_7_10_0) ? DiscoveryNodeRole.DATA_ROLE : this;
132+
}
117133
};
118134

119135
public static DiscoveryNodeRole DATA_COLD_NODE_ROLE = new DiscoveryNodeRole("data_cold", "c") {
@@ -131,6 +147,11 @@ public Setting<Boolean> legacySetting() {
131147
public boolean canContainData() {
132148
return true;
133149
}
150+
151+
@Override
152+
public DiscoveryNodeRole getCompatibilityRole(Version nodeVersion) {
153+
return nodeVersion.before(Version.V_7_10_0) ? DiscoveryNodeRole.DATA_ROLE : this;
154+
}
134155
};
135156

136157
public static boolean isContentNode(DiscoveryNode discoveryNode) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
apply plugin: 'elasticsearch.testclusters'
2+
apply plugin: 'elasticsearch.standalone-rest-test'
3+
apply from : "$rootDir/gradle/bwc-test.gradle"
4+
apply plugin: 'elasticsearch.rest-test'
5+
6+
import org.elasticsearch.gradle.Version
7+
import org.elasticsearch.gradle.VersionProperties
8+
import org.elasticsearch.gradle.info.BuildParams
9+
import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask
10+
11+
dependencies {
12+
testImplementation project(':x-pack:qa')
13+
}
14+
15+
// Only run tests for 7.9+, since the node.roles setting was introduced in 7.9.0
16+
for (Version bwcVersion : BuildParams.bwcVersions.wireCompatible.findAll { it.onOrAfter('7.9.0') }) {
17+
if (bwcVersion == VersionProperties.getElasticsearchVersion()) {
18+
// Not really a mixed cluster
19+
continue;
20+
}
21+
22+
String baseName = "v${bwcVersion}"
23+
24+
testClusters {
25+
"${baseName}" {
26+
versions = [bwcVersion.toString(), project.version]
27+
numberOfNodes = 3
28+
testDistribution = 'DEFAULT'
29+
setting 'xpack.security.enabled', 'false'
30+
setting 'xpack.watcher.enabled', 'false'
31+
setting 'xpack.ml.enabled', 'false'
32+
setting 'xpack.license.self_generated.type', 'trial'
33+
nodes."${baseName}-0".setting 'node.roles', '["master"]'
34+
// data_* roles were introduced in 7.10.0, so use 'data' for older versions
35+
if (bwcVersion.before('7.10.0')) {
36+
nodes."${baseName}-1".setting 'node.roles', '["data"]'
37+
} else {
38+
nodes."${baseName}-1".setting 'node.roles', '["data_content", "data_hot"]'
39+
}
40+
nodes."${baseName}-2".setting 'node.roles', '["master"]'
41+
}
42+
}
43+
44+
tasks.register("${baseName}#mixedClusterTest", StandaloneRestIntegTestTask) {
45+
useCluster testClusters."${baseName}"
46+
mustRunAfter(precommit)
47+
doFirst {
48+
// Getting the endpoints causes a wait for the cluster
49+
println "Endpoints are: ${-> testClusters."${baseName}".allHttpSocketURI.join(",")}"
50+
testClusters."${baseName}".nextNodeToNextVersion()
51+
52+
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}")
53+
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}")
54+
}
55+
onlyIf { project.bwc_tests_enabled }
56+
}
57+
58+
tasks.register(bwcTaskName(bwcVersion)) {
59+
dependsOn "${baseName}#mixedClusterTest"
60+
}
61+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.mixed;
8+
9+
import org.elasticsearch.cluster.metadata.IndexMetadata;
10+
import org.elasticsearch.common.settings.Settings;
11+
import org.elasticsearch.test.rest.ESRestTestCase;
12+
13+
public class DataTierMixedIT extends ESRestTestCase {
14+
15+
public void testMixedTierCompatibility() throws Exception {
16+
createIndex("test-index", Settings.builder()
17+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
18+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
19+
.build());
20+
ensureGreen("test-index");
21+
}
22+
}

0 commit comments

Comments
 (0)