Skip to content

Commit c69f13c

Browse files
committed
Expose minimum_master_nodes in cluster state
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 74d1cfb commit c69f13c

File tree

10 files changed

+119
-14
lines changed

10 files changed

+119
-14
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ private void buildResponse(final ClusterStateRequest request,
127127
ClusterState.Builder builder = ClusterState.builder(currentState.getClusterName());
128128
builder.version(currentState.version());
129129
builder.stateUUID(currentState.stateUUID());
130+
builder.minimumMasterNodesOnPublishingMaster(currentState.getMinimumMasterNodesOnPublishingMaster());
130131

131132
if (request.nodes()) {
132133
builder.nodes(currentState.nodes());

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.carrotsearch.hppc.cursors.ObjectCursor;
2424
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
2525

26+
import org.elasticsearch.Version;
2627
import org.elasticsearch.client.transport.TransportClient;
2728
import org.elasticsearch.cluster.block.ClusterBlock;
2829
import org.elasticsearch.cluster.block.ClusterBlocks;
@@ -178,17 +179,19 @@ default boolean isPrivate() {
178179

179180
private final boolean wasReadFromDiff;
180181

182+
private final int minimumMasterNodesOnPublishingMaster;
183+
181184
// built on demand
182185
private volatile RoutingNodes routingNodes;
183186

184187
public ClusterState(long version, String stateUUID, ClusterState state) {
185188
this(state.clusterName, version, stateUUID, state.metaData(), state.routingTable(), state.nodes(), state.blocks(),
186-
state.customs(), false);
189+
state.customs(), -1, false);
187190
}
188191

189192
public ClusterState(ClusterName clusterName, long version, String stateUUID, MetaData metaData, RoutingTable routingTable,
190193
DiscoveryNodes nodes, ClusterBlocks blocks, ImmutableOpenMap<String, Custom> customs,
191-
boolean wasReadFromDiff) {
194+
int minimumMasterNodesOnPublishingMaster, boolean wasReadFromDiff) {
192195
this.version = version;
193196
this.stateUUID = stateUUID;
194197
this.clusterName = clusterName;
@@ -197,6 +200,7 @@ public ClusterState(ClusterName clusterName, long version, String stateUUID, Met
197200
this.nodes = nodes;
198201
this.blocks = blocks;
199202
this.customs = customs;
203+
this.minimumMasterNodesOnPublishingMaster = minimumMasterNodesOnPublishingMaster;
200204
this.wasReadFromDiff = wasReadFromDiff;
201205
}
202206

@@ -290,6 +294,8 @@ public Set<VotingConfigExclusion> getVotingConfigExclusions() {
290294
return coordinationMetaData().getVotingConfigExclusions();
291295
}
292296

297+
public int getMinimumMasterNodesOnPublishingMaster() { return minimumMasterNodesOnPublishingMaster; }
298+
293299
// Used for testing and logging to determine how this cluster state was send over the wire
294300
public boolean wasReadFromDiff() {
295301
return wasReadFromDiff;
@@ -436,6 +442,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
436442

437443
if (metrics.contains(Metric.MASTER_NODE)) {
438444
builder.field("master_node", nodes().getMasterNodeId());
445+
builder.field("minimum_master_nodes", minimumMasterNodesOnPublishingMaster);
439446
}
440447

441448
if (metrics.contains(Metric.BLOCKS)) {
@@ -644,7 +651,7 @@ public static class Builder {
644651
private ClusterBlocks blocks = ClusterBlocks.EMPTY_CLUSTER_BLOCK;
645652
private final ImmutableOpenMap.Builder<String, Custom> customs;
646653
private boolean fromDiff;
647-
654+
private int minimumMasterNodesOnPublishingMaster = -1;
648655

649656
public Builder(ClusterState state) {
650657
this.clusterName = state.clusterName;
@@ -655,6 +662,7 @@ public Builder(ClusterState state) {
655662
this.metaData = state.metaData();
656663
this.blocks = state.blocks();
657664
this.customs = ImmutableOpenMap.builder(state.customs());
665+
this.minimumMasterNodesOnPublishingMaster = state.minimumMasterNodesOnPublishingMaster;
658666
this.fromDiff = false;
659667
}
660668

@@ -715,6 +723,11 @@ public Builder stateUUID(String uuid) {
715723
return this;
716724
}
717725

726+
public Builder minimumMasterNodesOnPublishingMaster(int minimumMasterNodesOnPublishingMaster) {
727+
this.minimumMasterNodesOnPublishingMaster = minimumMasterNodesOnPublishingMaster;
728+
return this;
729+
}
730+
718731
public Builder putCustom(String type, Custom custom) {
719732
customs.put(type, custom);
720733
return this;
@@ -739,7 +752,8 @@ public ClusterState build() {
739752
if (UNKNOWN_UUID.equals(uuid)) {
740753
uuid = UUIDs.randomBase64UUID();
741754
}
742-
return new ClusterState(clusterName, version, uuid, metaData, routingTable, nodes, blocks, customs.build(), fromDiff);
755+
return new ClusterState(clusterName, version, uuid, metaData, routingTable, nodes, blocks, customs.build(),
756+
minimumMasterNodesOnPublishingMaster, fromDiff);
743757
}
744758

745759
public static byte[] toBytes(ClusterState state) throws IOException {
@@ -782,6 +796,7 @@ public static ClusterState readFrom(StreamInput in, DiscoveryNode localNode) thr
782796
Custom customIndexMetaData = in.readNamedWriteable(Custom.class);
783797
builder.putCustom(customIndexMetaData.getWriteableName(), customIndexMetaData);
784798
}
799+
builder.minimumMasterNodesOnPublishingMaster = in.getVersion().onOrAfter(Version.V_7_0_0) ? in.readVInt() : -1;
785800
return builder.build();
786801
}
787802

@@ -807,6 +822,9 @@ public void writeTo(StreamOutput out) throws IOException {
807822
out.writeNamedWriteable(cursor.value);
808823
}
809824
}
825+
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
826+
out.writeVInt(minimumMasterNodesOnPublishingMaster);
827+
}
810828
}
811829

812830
private static class ClusterStateDiff implements Diff<ClusterState> {
@@ -829,6 +847,8 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
829847

830848
private final Diff<ImmutableOpenMap<String, Custom>> customs;
831849

850+
private final int minimumMasterNodesOnPublishingMaster;
851+
832852
ClusterStateDiff(ClusterState before, ClusterState after) {
833853
fromUuid = before.stateUUID;
834854
toUuid = after.stateUUID;
@@ -839,6 +859,7 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
839859
metaData = after.metaData.diff(before.metaData);
840860
blocks = after.blocks.diff(before.blocks);
841861
customs = DiffableUtils.diff(before.customs, after.customs, DiffableUtils.getStringKeySerializer(), CUSTOM_VALUE_SERIALIZER);
862+
minimumMasterNodesOnPublishingMaster = after.minimumMasterNodesOnPublishingMaster;
842863
}
843864

844865
ClusterStateDiff(StreamInput in, DiscoveryNode localNode) throws IOException {
@@ -851,6 +872,7 @@ private static class ClusterStateDiff implements Diff<ClusterState> {
851872
metaData = MetaData.readDiffFrom(in);
852873
blocks = ClusterBlocks.readDiffFrom(in);
853874
customs = DiffableUtils.readImmutableOpenMapDiff(in, DiffableUtils.getStringKeySerializer(), CUSTOM_VALUE_SERIALIZER);
875+
minimumMasterNodesOnPublishingMaster = in.getVersion().onOrAfter(Version.V_7_0_0) ? in.readVInt() : -1;
854876
}
855877

856878
@Override
@@ -864,6 +886,9 @@ public void writeTo(StreamOutput out) throws IOException {
864886
metaData.writeTo(out);
865887
blocks.writeTo(out);
866888
customs.writeTo(out);
889+
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
890+
out.writeVInt(minimumMasterNodesOnPublishingMaster);
891+
}
867892
}
868893

869894
@Override
@@ -883,9 +908,9 @@ public ClusterState apply(ClusterState state) {
883908
builder.metaData(metaData.apply(state.metaData));
884909
builder.blocks(blocks.apply(state.blocks));
885910
builder.customs(customs.apply(state.customs));
911+
builder.minimumMasterNodesOnPublishingMaster(minimumMasterNodesOnPublishingMaster);
886912
builder.fromDiff(true);
887913
return builder.build();
888914
}
889-
890915
}
891916
}

server/src/main/java/org/elasticsearch/cluster/coordination/JoinHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public JoinHelper(Settings settings, AllocationService allocationService, Master
9090
this.masterService = masterService;
9191
this.transportService = transportService;
9292
this.joinTimeout = JOIN_TIMEOUT_SETTING.get(settings);
93-
this.joinTaskExecutor = new JoinTaskExecutor(allocationService, logger) {
93+
this.joinTaskExecutor = new JoinTaskExecutor(settings, allocationService, logger) {
9494

9595
@Override
9696
public ClusterTasksResult<JoinTaskExecutor.Task> execute(ClusterState currentState, List<JoinTaskExecutor.Task> joiningTasks)

server/src/main/java/org/elasticsearch/cluster/coordination/JoinTaskExecutor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import org.elasticsearch.cluster.node.DiscoveryNode;
3030
import org.elasticsearch.cluster.node.DiscoveryNodes;
3131
import org.elasticsearch.cluster.routing.allocation.AllocationService;
32+
import org.elasticsearch.common.settings.Settings;
3233
import org.elasticsearch.discovery.DiscoverySettings;
34+
import org.elasticsearch.discovery.zen.ElectMasterService;
3335

3436
import java.util.ArrayList;
3537
import java.util.Collection;
@@ -45,6 +47,8 @@ public class JoinTaskExecutor implements ClusterStateTaskExecutor<JoinTaskExecut
4547

4648
private final Logger logger;
4749

50+
private final int minimumMasterNodesOnLocalNode;
51+
4852
public static class Task {
4953

5054
private final DiscoveryNode node;
@@ -80,9 +84,10 @@ public boolean isFinishElectionTask() {
8084
private static final String FINISH_ELECTION_TASK_REASON = "_FINISH_ELECTION_";
8185
}
8286

83-
public JoinTaskExecutor(AllocationService allocationService, Logger logger) {
87+
public JoinTaskExecutor(Settings settings, AllocationService allocationService, Logger logger) {
8488
this.allocationService = allocationService;
8589
this.logger = logger;
90+
minimumMasterNodesOnLocalNode = ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.get(settings);
8691
}
8792

8893
@Override
@@ -185,7 +190,9 @@ protected ClusterState.Builder becomeMasterAndTrimConflictingNodes(ClusterState
185190
// or removed by us above
186191
ClusterState tmpState = ClusterState.builder(currentState).nodes(nodesBuilder).blocks(ClusterBlocks.builder()
187192
.blocks(currentState.blocks())
188-
.removeGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID)).build();
193+
.removeGlobalBlock(DiscoverySettings.NO_MASTER_BLOCK_ID))
194+
.minimumMasterNodesOnPublishingMaster(minimumMasterNodesOnLocalNode)
195+
.build();
189196
logger.trace("becomeMasterAndTrimConflictingNodes: {}", tmpState.nodes());
190197
return ClusterState.builder(allocationService.deassociateDeadNodes(tmpState, false, "removed dead nodes on election"));
191198
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.cluster.routing.allocation.AllocationService;
3333
import org.elasticsearch.cluster.service.MasterService;
3434
import org.elasticsearch.common.Priority;
35+
import org.elasticsearch.common.settings.Settings;
3536
import org.elasticsearch.common.unit.TimeValue;
3637

3738
import java.util.ArrayList;
@@ -59,9 +60,10 @@ public class NodeJoinController {
5960
private ElectionContext electionContext = null;
6061

6162

62-
public NodeJoinController(MasterService masterService, AllocationService allocationService, ElectMasterService electMaster) {
63+
public NodeJoinController(Settings settings, MasterService masterService, AllocationService allocationService,
64+
ElectMasterService electMaster) {
6365
this.masterService = masterService;
64-
joinTaskExecutor = new JoinTaskExecutor(allocationService, logger) {
66+
joinTaskExecutor = new JoinTaskExecutor(settings, allocationService, logger) {
6567
@Override
6668
public void clusterStatePublished(ClusterChangedEvent event) {
6769
electMaster.logMinimumMasterNodesWarningIfNecessary(event.previousState(), event.state());

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

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

223-
this.nodeJoinController = new NodeJoinController(masterService, allocationService, electMaster);
223+
this.nodeJoinController = new NodeJoinController(settings, masterService, allocationService, electMaster);
224224
this.nodeRemovalExecutor = new ZenNodeRemovalClusterStateTaskExecutor(allocationService, electMaster, this::submitRejoin, logger);
225225

226226
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
@@ -213,7 +213,7 @@ allocationService, new AliasValidator(), environment,
213213
transportService, clusterService, threadPool, createIndexService, actionFilters, indexNameExpressionResolver);
214214

215215
nodeRemovalExecutor = new NodeRemovalClusterStateTaskExecutor(allocationService, logger);
216-
joinTaskExecutor = new JoinTaskExecutor(allocationService, logger);
216+
joinTaskExecutor = new JoinTaskExecutor(Settings.EMPTY, allocationService, logger);
217217
}
218218

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

0 commit comments

Comments
 (0)