Skip to content

Commit 4f08fe0

Browse files
authored
Expose minimum_master_nodes in cluster state (#37811)
To safely support rolling upgrades from 6.x to 7.x we need the 7.x nodes to have access to the `minimum_master_nodes` setting, but this setting is otherwise unnecessary in 7.x and we would like to remove it. Since a rolling upgrade from 6.x to 7.x involves the 7.x nodes joining a 6.x master, we can avoid the need for setting `minimum_master_nodes` on the 7.x nodes by copying the value set on the 6.x master. This change exposes the master's node-level value for `minimum_master_nodes` via a field in the cluster state.
1 parent 4a1e66e commit 4f08fe0

File tree

8 files changed

+128
-12
lines changed

8 files changed

+128
-12
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/state/TransportClusterStateAction.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ private void buildResponse(final ClusterStateRequest request,
128128
ClusterState.Builder builder = ClusterState.builder(currentState.getClusterName());
129129
builder.version(currentState.version());
130130
builder.stateUUID(currentState.stateUUID());
131+
builder.minimumMasterNodesOnPublishingMaster(currentState.getMinimumMasterNodesOnPublishingMaster());
132+
131133
if (request.nodes()) {
132134
builder.nodes(currentState.nodes());
133135
}

server/src/main/java/org/elasticsearch/cluster/ClusterState.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import com.carrotsearch.hppc.cursors.IntObjectCursor;
2323
import com.carrotsearch.hppc.cursors.ObjectCursor;
2424
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
25+
26+
import org.elasticsearch.Version;
2527
import org.elasticsearch.client.transport.TransportClient;
2628
import org.elasticsearch.cluster.block.ClusterBlock;
2729
import org.elasticsearch.cluster.block.ClusterBlocks;
@@ -172,16 +174,19 @@ default boolean isPrivate() {
172174

173175
private final boolean wasReadFromDiff;
174176

177+
private final int minimumMasterNodesOnPublishingMaster;
178+
175179
// built on demand
176180
private volatile RoutingNodes routingNodes;
177181

178182
public ClusterState(long version, String stateUUID, ClusterState state) {
179183
this(state.clusterName, version, stateUUID, state.metaData(), state.routingTable(), state.nodes(), state.blocks(), state.customs(),
180-
false);
184+
-1, false);
181185
}
182186

183187
public ClusterState(ClusterName clusterName, long version, String stateUUID, MetaData metaData, RoutingTable routingTable,
184-
DiscoveryNodes nodes, ClusterBlocks blocks, ImmutableOpenMap<String, Custom> customs, boolean wasReadFromDiff) {
188+
DiscoveryNodes nodes, ClusterBlocks blocks, ImmutableOpenMap<String, Custom> customs,
189+
int minimumMasterNodesOnPublishingMaster, boolean wasReadFromDiff) {
185190
this.version = version;
186191
this.stateUUID = stateUUID;
187192
this.clusterName = clusterName;
@@ -190,6 +195,7 @@ public ClusterState(ClusterName clusterName, long version, String stateUUID, Met
190195
this.nodes = nodes;
191196
this.blocks = blocks;
192197
this.customs = customs;
198+
this.minimumMasterNodesOnPublishingMaster = minimumMasterNodesOnPublishingMaster;
193199
this.wasReadFromDiff = wasReadFromDiff;
194200
}
195201

@@ -257,6 +263,17 @@ public ClusterName getClusterName() {
257263
return this.clusterName;
258264
}
259265

266+
/**
267+
* The node-level `discovery.zen.minimum_master_nodes` setting on the master node that published this cluster state, for use in rolling
268+
* upgrades from 6.x to 7.x. Once all the 6.x master-eligible nodes have left the cluster, the 7.x nodes use this value to determine how
269+
* many master-eligible nodes must be discovered before the cluster can be bootstrapped. Note that this method returns the node-level
270+
* value of this setting, and ignores any cluster-level override that was set via the API. Callers are expected to combine this value
271+
* with any value set in the cluster-level settings. This should be removed once we no longer need support for {@link Version#V_6_7_0}.
272+
*/
273+
public int getMinimumMasterNodesOnPublishingMaster() {
274+
return minimumMasterNodesOnPublishingMaster;
275+
}
276+
260277
// Used for testing and logging to determine how this cluster state was send over the wire
261278
public boolean wasReadFromDiff() {
262279
return wasReadFromDiff;
@@ -598,7 +615,7 @@ public static class Builder {
598615
private ClusterBlocks blocks = ClusterBlocks.EMPTY_CLUSTER_BLOCK;
599616
private final ImmutableOpenMap.Builder<String, Custom> customs;
600617
private boolean fromDiff;
601-
618+
private int minimumMasterNodesOnPublishingMaster = -1;
602619

603620
public Builder(ClusterState state) {
604621
this.clusterName = state.clusterName;
@@ -609,6 +626,7 @@ public Builder(ClusterState state) {
609626
this.metaData = state.metaData();
610627
this.blocks = state.blocks();
611628
this.customs = ImmutableOpenMap.builder(state.customs());
629+
this.minimumMasterNodesOnPublishingMaster = state.minimumMasterNodesOnPublishingMaster;
612630
this.fromDiff = false;
613631
}
614632

@@ -669,6 +687,11 @@ public Builder stateUUID(String uuid) {
669687
return this;
670688
}
671689

690+
public Builder minimumMasterNodesOnPublishingMaster(int minimumMasterNodesOnPublishingMaster) {
691+
this.minimumMasterNodesOnPublishingMaster = minimumMasterNodesOnPublishingMaster;
692+
return this;
693+
}
694+
672695
public Builder putCustom(String type, Custom custom) {
673696
customs.put(type, custom);
674697
return this;
@@ -693,7 +716,8 @@ public ClusterState build() {
693716
if (UNKNOWN_UUID.equals(uuid)) {
694717
uuid = UUIDs.randomBase64UUID();
695718
}
696-
return new ClusterState(clusterName, version, uuid, metaData, routingTable, nodes, blocks, customs.build(), fromDiff);
719+
return new ClusterState(clusterName, version, uuid, metaData, routingTable, nodes, blocks, customs.build(),
720+
minimumMasterNodesOnPublishingMaster, fromDiff);
697721
}
698722

699723
public static byte[] toBytes(ClusterState state) throws IOException {
@@ -736,6 +760,7 @@ public static ClusterState readFrom(StreamInput in, DiscoveryNode localNode) thr
736760
Custom customIndexMetaData = in.readNamedWriteable(Custom.class);
737761
builder.putCustom(customIndexMetaData.getWriteableName(), customIndexMetaData);
738762
}
763+
builder.minimumMasterNodesOnPublishingMaster = in.getVersion().onOrAfter(Version.V_6_7_0) ? in.readVInt() : -1;
739764
return builder.build();
740765
}
741766

@@ -761,6 +786,9 @@ public void writeTo(StreamOutput out) throws IOException {
761786
out.writeNamedWriteable(cursor.value);
762787
}
763788
}
789+
if (out.getVersion().onOrAfter(Version.V_6_7_0)) {
790+
out.writeVInt(minimumMasterNodesOnPublishingMaster);
791+
}
764792
}
765793

766794
private static class ClusterStateDiff implements Diff<ClusterState> {
@@ -783,6 +811,8 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
783811

784812
private final Diff<ImmutableOpenMap<String, Custom>> customs;
785813

814+
private final int minimumMasterNodesOnPublishingMaster;
815+
786816
ClusterStateDiff(ClusterState before, ClusterState after) {
787817
fromUuid = before.stateUUID;
788818
toUuid = after.stateUUID;
@@ -793,6 +823,7 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
793823
metaData = after.metaData.diff(before.metaData);
794824
blocks = after.blocks.diff(before.blocks);
795825
customs = DiffableUtils.diff(before.customs, after.customs, DiffableUtils.getStringKeySerializer(), CUSTOM_VALUE_SERIALIZER);
826+
minimumMasterNodesOnPublishingMaster = after.minimumMasterNodesOnPublishingMaster;
796827
}
797828

798829
ClusterStateDiff(StreamInput in, DiscoveryNode localNode) throws IOException {
@@ -805,6 +836,7 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
805836
metaData = MetaData.readDiffFrom(in);
806837
blocks = ClusterBlocks.readDiffFrom(in);
807838
customs = DiffableUtils.readImmutableOpenMapDiff(in, DiffableUtils.getStringKeySerializer(), CUSTOM_VALUE_SERIALIZER);
839+
minimumMasterNodesOnPublishingMaster = in.getVersion().onOrAfter(Version.V_6_7_0) ? in.readVInt() : -1;
808840
}
809841

810842
@Override
@@ -818,6 +850,9 @@ public void writeTo(StreamOutput out) throws IOException {
818850
metaData.writeTo(out);
819851
blocks.writeTo(out);
820852
customs.writeTo(out);
853+
if (out.getVersion().onOrAfter(Version.V_6_7_0)) {
854+
out.writeVInt(minimumMasterNodesOnPublishingMaster);
855+
}
821856
}
822857

823858
@Override
@@ -837,6 +872,7 @@ public ClusterState apply(ClusterState state) {
837872
builder.metaData(metaData.apply(state.metaData));
838873
builder.blocks(blocks.apply(state.blocks));
839874
builder.customs(customs.apply(state.customs));
875+
builder.minimumMasterNodesOnPublishingMaster(minimumMasterNodesOnPublishingMaster);
840876
builder.fromDiff(true);
841877
return builder.build();
842878
}

server/src/main/java/org/elasticsearch/discovery/zen/NodeJoinController.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.cluster.routing.allocation.AllocationService;
3636
import org.elasticsearch.cluster.service.MasterService;
3737
import org.elasticsearch.common.Priority;
38+
import org.elasticsearch.common.settings.Settings;
3839
import org.elasticsearch.common.transport.TransportAddress;
3940
import org.elasticsearch.common.unit.TimeValue;
4041
import org.elasticsearch.discovery.DiscoverySettings;
@@ -67,9 +68,10 @@ public class NodeJoinController {
6768
private ElectionContext electionContext = null;
6869

6970

70-
public NodeJoinController(MasterService masterService, AllocationService allocationService, ElectMasterService electMaster) {
71+
public NodeJoinController(Settings settings, MasterService masterService, AllocationService allocationService,
72+
ElectMasterService electMaster) {
7173
this.masterService = masterService;
72-
joinTaskExecutor = new JoinTaskExecutor(allocationService, electMaster, logger);
74+
joinTaskExecutor = new JoinTaskExecutor(settings, allocationService, electMaster, logger);
7375
}
7476

7577
/**
@@ -410,10 +412,14 @@ public static class JoinTaskExecutor implements ClusterStateTaskExecutor<Discove
410412

411413
private final Logger logger;
412414

413-
public JoinTaskExecutor(AllocationService allocationService, ElectMasterService electMasterService, Logger logger) {
415+
private final int minimumMasterNodesOnLocalNode;
416+
417+
public JoinTaskExecutor(Settings settings, AllocationService allocationService, ElectMasterService electMasterService,
418+
Logger logger) {
414419
this.allocationService = allocationService;
415420
this.electMasterService = electMasterService;
416421
this.logger = logger;
422+
minimumMasterNodesOnLocalNode = ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.get(settings);
417423
}
418424

419425
@Override
@@ -509,7 +515,9 @@ private ClusterState.Builder becomeMasterAndTrimConflictingNodes(ClusterState cu
509515
// or removed by us above
510516
ClusterState tmpState = ClusterState.builder(currentState).nodes(nodesBuilder).blocks(ClusterBlocks.builder()
511517
.blocks(currentState.blocks())
512-
.removeGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID)).build();
518+
.removeGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID))
519+
.minimumMasterNodesOnPublishingMaster(minimumMasterNodesOnLocalNode)
520+
.build();
513521
tmpState = PersistentTasksCustomMetaData.disassociateDeadNodes(tmpState);
514522
return ClusterState.builder(allocationService.disassociateDeadNodes(tmpState, false,
515523
"removed dead nodes on election"));

server/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ public ZenDiscovery(Settings settings, ThreadPool threadPool, TransportService t
219219
this.membership = new MembershipAction(transportService, new MembershipListener(), onJoinValidators);
220220
this.joinThreadControl = new JoinThreadControl();
221221

222-
this.nodeJoinController = new NodeJoinController(masterService, allocationService, electMaster);
222+
this.nodeJoinController = new NodeJoinController(settings, masterService, allocationService, electMaster);
223223
this.nodeRemovalExecutor = new NodeRemovalClusterStateTaskExecutor(allocationService, electMaster, this::submitRejoin, logger);
224224

225225
masterService.setClusterStateSupplier(this::clusterState);

server/src/test/java/org/elasticsearch/cluster/serialization/ClusterSerializationTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public void testClusterStateSerialization() throws Exception {
6767
.add(newNode("node3")).localNodeId("node1").masterNodeId("node2").build();
6868

6969
ClusterState clusterState = ClusterState.builder(new ClusterName("clusterName1"))
70-
.nodes(nodes).metaData(metaData).routingTable(routingTable).build();
70+
.nodes(nodes).metaData(metaData).routingTable(routingTable)
71+
.minimumMasterNodesOnPublishingMaster(randomIntBetween(-1, 10)).build();
7172

7273
AllocationService strategy = createAllocationService();
7374
clusterState = ClusterState.builder(clusterState).routingTable(strategy.reroute(clusterState, "reroute").routingTable()).build();
@@ -78,6 +79,9 @@ public void testClusterStateSerialization() throws Exception {
7879
assertThat(serializedClusterState.getClusterName().value(), equalTo(clusterState.getClusterName().value()));
7980

8081
assertThat(serializedClusterState.routingTable().toString(), equalTo(clusterState.routingTable().toString()));
82+
83+
assertThat(serializedClusterState.getMinimumMasterNodesOnPublishingMaster(),
84+
equalTo(clusterState.getMinimumMasterNodesOnPublishingMaster()));
8185
}
8286

8387
public void testRoutingTableSerialization() throws Exception {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.discovery.zen;
20+
21+
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
22+
import org.elasticsearch.cluster.ClusterState;
23+
import org.elasticsearch.test.ESIntegTestCase;
24+
25+
import java.util.List;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
28+
29+
import static org.elasticsearch.discovery.zen.ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING;
30+
import static org.elasticsearch.test.InternalTestCluster.nameFilter;
31+
import static org.hamcrest.Matchers.equalTo;
32+
import static org.hamcrest.Matchers.isIn;
33+
34+
@ESIntegTestCase.ClusterScope(numDataNodes = 0, numClientNodes = 0)
35+
public class MinimumMasterNodesInClusterStateIT extends ESIntegTestCase {
36+
37+
public void testMasterPublishes() throws Exception {
38+
final String firstNode = internalCluster().startNode();
39+
40+
{
41+
final ClusterState localState
42+
= client(firstNode).admin().cluster().state(new ClusterStateRequest().local(true)).get().getState();
43+
assertThat(localState.getMinimumMasterNodesOnPublishingMaster(), equalTo(1));
44+
assertFalse(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.exists(localState.metaData().settings()));
45+
}
46+
47+
final List<String> secondThirdNodes = internalCluster().startNodes(2);
48+
assertThat(internalCluster().getMasterName(), equalTo(firstNode));
49+
50+
final List<String> allNodes = Stream.concat(Stream.of(firstNode), secondThirdNodes.stream()).collect(Collectors.toList());
51+
for (final String node : allNodes) {
52+
final ClusterState localState = client(node).admin().cluster().state(new ClusterStateRequest().local(true)).get().getState();
53+
assertThat(localState.getMinimumMasterNodesOnPublishingMaster(), equalTo(1));
54+
assertThat(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.get(localState.metaData().settings()), equalTo(2));
55+
}
56+
57+
internalCluster().stopRandomNode(nameFilter(firstNode));
58+
assertThat(internalCluster().getMasterName(), isIn(secondThirdNodes));
59+
60+
for (final String node : secondThirdNodes) {
61+
final ClusterState localState = client(node).admin().cluster().state(new ClusterStateRequest().local(true)).get().getState();
62+
assertThat(localState.getMinimumMasterNodesOnPublishingMaster(), equalTo(2));
63+
assertThat(DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.get(localState.metaData().settings()), equalTo(2));
64+
}
65+
}
66+
}

server/src/test/java/org/elasticsearch/discovery/zen/NodeJoinControllerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ private void setupMasterServiceAndNodeJoinController(ClusterState initialState)
141141
throw new IllegalStateException("method setupMasterServiceAndNodeJoinController can only be called once");
142142
}
143143
masterService = ClusterServiceUtils.createMasterService(threadPool, initialState);
144-
nodeJoinController = new NodeJoinController(masterService, createAllocationService(Settings.EMPTY),
144+
nodeJoinController = new NodeJoinController(Settings.EMPTY, masterService, createAllocationService(Settings.EMPTY),
145145
new ElectMasterService(Settings.EMPTY));
146146
}
147147

server/src/test/java/org/elasticsearch/indices/cluster/ClusterStateChanges.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ allocationService, new AliasValidator(), environment,
215215
ElectMasterService electMasterService = new ElectMasterService(SETTINGS);
216216
nodeRemovalExecutor = new ZenDiscovery.NodeRemovalClusterStateTaskExecutor(allocationService, electMasterService,
217217
s -> { throw new AssertionError("rejoin not implemented"); }, logger);
218-
joinTaskExecutor = new NodeJoinController.JoinTaskExecutor(allocationService, electMasterService, logger);
218+
joinTaskExecutor = new NodeJoinController.JoinTaskExecutor(Settings.EMPTY, allocationService, electMasterService, logger);
219219
}
220220

221221
public ClusterState createIndex(ClusterState state, CreateIndexRequest request) {

0 commit comments

Comments
 (0)