This is an automated email from the ASF dual-hosted git repository.

williamsong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ea4f02f4 RATIS-1930. Add a conf for enable/disable majority-add. 
(#961)
5ea4f02f4 is described below

commit 5ea4f02f4aac920eefd023cb2e3fb359b06c400a
Author: Tsz-Wo Nicholas Sze <[email protected]>
AuthorDate: Mon Nov 6 18:59:50 2023 -0800

    RATIS-1930. Add a conf for enable/disable majority-add. (#961)
---
 ratis-docs/src/site/markdown/configurations.md      | 19 ++++++++++++++++++-
 .../apache/ratis/server/RaftServerConfigKeys.java   | 21 ++++++++++++++++++++-
 .../apache/ratis/server/impl/RaftServerImpl.java    | 11 ++++++++---
 .../ratis/InstallSnapshotFromLeaderTests.java       | 10 ++++------
 4 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/ratis-docs/src/site/markdown/configurations.md 
b/ratis-docs/src/site/markdown/configurations.md
index 4fc3c5caa..dd953e7fd 100644
--- a/ratis-docs/src/site/markdown/configurations.md
+++ b/ratis-docs/src/site/markdown/configurations.md
@@ -646,7 +646,7 @@ First election timeout is introduced to reduce unavailable 
time when a RaftGroup
 | **Property**    | `raft.server.leaderelection.pre-vote` |
 |:----------------|:--------------------------------------|
 | **Description** | enable pre-vote                       |
-| **Type**        | bool                                  |
+| **Type**        | boolean                               |
 | **Default**     | true                                  |
 
 In Pre-Vote, the candidate does not change its term and try to learn
@@ -654,3 +654,20 @@ if a majority of the cluster would be willing to grant the 
candidate their votes
 (if the candidate’s log is sufficiently up-to-date, 
 and the voters have not received heartbeats from a valid leader
 for at least a baseline election timeout).
+
+| **Property**    | `raft.server.leaderelection.member.majority-add` |
+|:----------------|:-------------------------------------------------|
+| **Description** | enable majority-add                              |
+| **Type**        | boolean                                          |
+| **Default**     | false                                            |
+
+Does it allow *majority-add*, i.e. adding a majority of members in a single 
setConf?
+
+- Note that, when a single setConf removes and adds members at the same time,
+the majority is counted after the removal.
+For examples,
+  1. setConf to a 3-member group by adding 2 new members is NOT a majority-add.
+  2. However, setConf to a 3-member group by removing 2 of members and adding 
2 new members is a majority-add.
+
+- Note also that adding 1 new member to an 1-member group is always allowed,
+  although it is a majority-add.
\ No newline at end of file
diff --git 
a/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
 
b/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
index a59bd8b37..f2de074a8 100644
--- 
a/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
+++ 
b/ratis-server-api/src/main/java/org/apache/ratis/server/RaftServerConfigKeys.java
@@ -881,10 +881,29 @@ public interface RaftServerConfigKeys {
     static boolean preVote(RaftProperties properties) {
       return getBoolean(properties::getBoolean, PRE_VOTE_KEY, 
PRE_VOTE_DEFAULT, getDefaultLog());
     }
-
     static void setPreVote(RaftProperties properties, boolean enablePreVote) {
       setBoolean(properties::setBoolean, PRE_VOTE_KEY, enablePreVote);
     }
+
+    /**
+     * Does it allow majority-add, i.e. adding a majority of members in a 
single setConf?
+     * <p>
+     * Note that, when a single setConf removes and adds members at the same 
time,
+     * the majority is counted after the removal.
+     * For examples, setConf to a 3-member group by adding 2 new members is 
NOT a majority-add.
+     * However, setConf to a 3-member group by removing 2 of members and 
adding 2 new members is a majority-add.
+     * <p>
+     * Note also that adding 1 new member to an 1-member group is always 
allowed,
+     * although it is a majority-add.
+     */
+    String MEMBER_MAJORITY_ADD_KEY = PREFIX + ".member.majority-add";
+    boolean MEMBER_MAJORITY_ADD_DEFAULT = false;
+    static boolean memberMajorityAdd(RaftProperties properties) {
+      return getBoolean(properties::getBoolean, MEMBER_MAJORITY_ADD_KEY, 
MEMBER_MAJORITY_ADD_DEFAULT, getDefaultLog());
+    }
+    static void setMemberMajorityAdd(RaftProperties properties, boolean 
enableMemberMajorityAdd) {
+      setBoolean(properties::setBoolean, MEMBER_MAJORITY_ADD_KEY, 
enableMemberMajorityAdd);
+    }
   }
 
   static void main(String[] args) {
diff --git 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
index 2cbd3146a..5e6c81215 100644
--- 
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
+++ 
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
@@ -214,6 +214,7 @@ class RaftServerImpl implements RaftServer.Division,
 
   private final DivisionProperties divisionProperties;
   private final TimeDuration leaderStepDownWaitTime;
+  private final boolean memberMajorityAddEnabled;
   private final TimeDuration sleepDeviationThreshold;
 
   private final LifeCycle lifeCycle;
@@ -259,7 +260,8 @@ class RaftServerImpl implements RaftServer.Division,
 
     final RaftProperties properties = proxy.getProperties();
     this.divisionProperties = new DivisionPropertiesImpl(properties);
-    leaderStepDownWaitTime = 
RaftServerConfigKeys.LeaderElection.leaderStepDownWaitTime(properties);
+    this.leaderStepDownWaitTime = 
RaftServerConfigKeys.LeaderElection.leaderStepDownWaitTime(properties);
+    this.memberMajorityAddEnabled = 
RaftServerConfigKeys.LeaderElection.memberMajorityAdd(properties);
     this.sleepDeviationThreshold = 
RaftServerConfigKeys.sleepDeviationThreshold(properties);
     this.proxy = proxy;
 
@@ -1314,8 +1316,11 @@ class RaftServerImpl implements RaftServer.Division,
         return pending.getFuture();
       }
       if (current.changeMajority(serversInNewConf)) {
-        throw new SetConfigurationException("Failed to set configuration: 
request " + request
-            + " changes a majority set of the current configuration " + 
current);
+        if (!memberMajorityAddEnabled) {
+          throw new SetConfigurationException("Failed to set configuration: 
request " + request
+              + " changes a majority set of the current configuration " + 
current);
+        }
+        LOG.warn("Try to add/replace a majority of servers in a single 
setConf: {}", request);
       }
 
       getRaftServer().addRaftPeers(serversInNewConf);
diff --git 
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
 
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
index e51c98548..15dafb88c 100644
--- 
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
+++ 
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
@@ -21,7 +21,6 @@ import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.conf.RaftProperties;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftGroupId;
-import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.server.RaftServer;
 import org.apache.ratis.server.RaftServerConfigKeys;
@@ -65,11 +64,11 @@ public abstract class 
InstallSnapshotFromLeaderTests<CLUSTER extends MiniRaftClu
 
   {
     final RaftProperties prop = getProperties();
-    RaftServerConfigKeys.Log.setPurgeGap(prop, PURGE_GAP);
-    RaftServerConfigKeys.Snapshot.setAutoTriggerThreshold(
-        prop, SNAPSHOT_TRIGGER_THRESHOLD);
     RaftServerConfigKeys.Snapshot.setAutoTriggerEnabled(prop, true);
+    RaftServerConfigKeys.Snapshot.setAutoTriggerThreshold(prop, 
SNAPSHOT_TRIGGER_THRESHOLD);
+    RaftServerConfigKeys.Log.setPurgeGap(prop, PURGE_GAP);
     RaftServerConfigKeys.Log.Appender.setSnapshotChunkSizeMax(prop, 
SizeInBytes.ONE_KB);
+    RaftServerConfigKeys.LeaderElection.setMemberMajorityAdd(prop, true);
   }
 
   private static final int SNAPSHOT_TRIGGER_THRESHOLD = 64;
@@ -112,8 +111,7 @@ public abstract class 
InstallSnapshotFromLeaderTests<CLUSTER extends MiniRaftClu
       final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true,
           true);
       // trigger setConfiguration
-      RaftServerTestUtil.runWithMinorityPeers(cluster, 
Arrays.asList(change.allPeersInNewConf),
-          peers -> 
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
+      cluster.setConfiguration(change.allPeersInNewConf);
 
       RaftServerTestUtil
           .waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);

Reply via email to