This is an automated email from the ASF dual-hosted git repository.
szetszwo 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 c35f769f5 RATIS-1912. Fix infinity election when perform membership
change. (#954)
c35f769f5 is described below
commit c35f769f513609d808ab1cc91c5323d9ff30f636
Author: Jinglun <[email protected]>
AuthorDate: Sat Nov 4 04:46:25 2023 +0800
RATIS-1912. Fix infinity election when perform membership change. (#954)
---
.../apache/ratis/server/impl/LeaderElection.java | 11 ++-
.../ratis/server/impl/RaftConfigurationImpl.java | 28 +++++++
.../apache/ratis/server/impl/RaftServerImpl.java | 4 +
.../ratis/InstallSnapshotFromLeaderTests.java | 4 +-
.../ratis/InstallSnapshotNotificationTests.java | 17 ++--
.../java/org/apache/ratis/RetryCacheTests.java | 4 +-
.../ratis/server/impl/GroupManagementBaseTest.java | 3 +-
.../server/impl/RaftReconfigurationBaseTest.java | 65 ++++++++++++++-
.../ratis/server/impl/RaftServerTestUtil.java | 47 +++++++++++
.../ratis/statemachine/RaftSnapshotBaseTest.java | 8 +-
.../ratis/server/impl/TestRaftConfiguration.java | 94 +++++++++++++++++++++-
11 files changed, 267 insertions(+), 18 deletions(-)
diff --git
a/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderElection.java
b/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderElection.java
index 302f84871..43b778057 100644
---
a/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderElection.java
+++
b/ratis-server/src/main/java/org/apache/ratis/server/impl/LeaderElection.java
@@ -104,7 +104,7 @@ class LeaderElection implements Runnable {
ELECTION
}
- enum Result {PASSED, REJECTED, TIMEOUT, DISCOVERED_A_NEW_TERM, SHUTDOWN,
NOT_IN_CONF}
+ enum Result {PASSED, SINGLE_MODE_PASSED, REJECTED, TIMEOUT,
DISCOVERED_A_NEW_TERM, SHUTDOWN, NOT_IN_CONF}
private static class ResultAndTerm {
private final Result result;
@@ -331,6 +331,7 @@ class LeaderElection implements Runnable {
switch (r.getResult()) {
case PASSED:
+ case SINGLE_MODE_PASSED:
return true;
case NOT_IN_CONF:
case SHUTDOWN:
@@ -379,6 +380,7 @@ class LeaderElection implements Runnable {
Collection<RaftPeerId> votedPeers = new ArrayList<>();
Collection<RaftPeerId> rejectedPeers = new ArrayList<>();
Set<RaftPeerId> higherPriorityPeers = getHigherPriorityPeers(conf);
+ final boolean singleMode = conf.isSingleMode(server.getId());
while (waitForNum > 0 && shouldRun(electionTerm)) {
final TimeDuration waitTime = timeout.elapsedTime().apply(n -> -n);
@@ -387,6 +389,9 @@ class LeaderElection implements Runnable {
// if some higher priority peer did not response when timeout, but
candidate get majority,
// candidate pass vote
return logAndReturn(phase, Result.PASSED, responses, exceptions);
+ } else if (singleMode) {
+ // if candidate is in single mode, candidate pass vote.
+ return logAndReturn(phase, Result.SINGLE_MODE_PASSED, responses,
exceptions);
} else {
return logAndReturn(phase, Result.TIMEOUT, responses, exceptions);
}
@@ -418,7 +423,7 @@ class LeaderElection implements Runnable {
}
// If any peer with higher priority rejects vote, candidate can not
pass vote
- if (!r.getServerReply().getSuccess() &&
higherPriorityPeers.contains(replierId)) {
+ if (!r.getServerReply().getSuccess() &&
higherPriorityPeers.contains(replierId) && !singleMode) {
return logAndReturn(phase, Result.REJECTED, responses, exceptions);
}
@@ -447,6 +452,8 @@ class LeaderElection implements Runnable {
// received all the responses
if (conf.hasMajority(votedPeers, server.getId())) {
return logAndReturn(phase, Result.PASSED, responses, exceptions);
+ } else if (singleMode) {
+ return logAndReturn(phase, Result.SINGLE_MODE_PASSED, responses,
exceptions);
} else {
return logAndReturn(phase, Result.REJECTED, responses, exceptions);
}
diff --git
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftConfigurationImpl.java
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftConfigurationImpl.java
index aba5ae176..d609264af 100644
---
a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftConfigurationImpl.java
+++
b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftConfigurationImpl.java
@@ -232,6 +232,34 @@ final class RaftConfigurationImpl implements
RaftConfiguration {
return others;
}
+ /**
+ * @return true if the new peers number reaches half of new conf peers
number or the group is
+ * changing from single mode to HA mode.
+ */
+ boolean changeMajority(Collection<RaftPeer> newMembers) {
+ Preconditions.assertNull(oldConf, "oldConf");
+ final long newPeersCount =
newMembers.stream().map(RaftPeer::getId).filter(id -> conf.getPeer(id) ==
null).count();
+
+ if (conf.size() == 1 && newMembers.size() == 2 && newPeersCount == 1) {
+ // Change from single peer to HA mode. This is a special case, skip
majority verification.
+ return false;
+ }
+
+ // If newPeersCount reaches majority number of new conf size, the cluster
may end with infinity
+ // election. See https://issues.apache.org/jira/browse/RATIS-1912 for more
details.
+ final long oldPeersCount = newMembers.size() - newPeersCount;
+ return newPeersCount >= oldPeersCount;
+ }
+
+ /** @return True if the selfId is in single mode. */
+ boolean isSingleMode(RaftPeerId selfId) {
+ if (isStable()) {
+ return conf.size() == 1;
+ } else {
+ return oldConf.size() == 1 && oldConf.contains(selfId) && conf.size() ==
2 && conf.contains(selfId);
+ }
+ }
+
/** @return true if the self id together with the others are in the
majority. */
boolean hasMajority(Collection<RaftPeerId> others, RaftPeerId selfId) {
Preconditions.assertTrue(!others.contains(selfId));
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 8005be894..b822870a4 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
@@ -1313,6 +1313,10 @@ class RaftServerImpl implements RaftServer.Division,
pending.setReply(newSuccessReply(request));
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);
+ }
getRaftServer().addRaftPeers(serversInNewConf);
getRaftServer().addRaftPeers(listenersInNewConf);
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 b7eb75204..e51c98548 100644
---
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
+++
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotFromLeaderTests.java
@@ -21,6 +21,7 @@ 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;
@@ -111,7 +112,8 @@ public abstract class
InstallSnapshotFromLeaderTests<CLUSTER extends MiniRaftClu
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true,
true);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
RaftServerTestUtil
.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
diff --git
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
index 2d7751980..72ddd06f2 100644
---
a/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
+++
b/ratis-server/src/test/java/org/apache/ratis/InstallSnapshotNotificationTests.java
@@ -49,6 +49,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -242,7 +243,8 @@ public abstract class
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true,
true);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
RaftServerTestUtil
.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
@@ -391,7 +393,8 @@ public abstract class
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true,
true);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
RaftServerTestUtil
.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
@@ -478,7 +481,8 @@ public abstract class
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
// add one new peer
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(1, true,
true);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
RaftServerTestUtil
.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
@@ -556,7 +560,8 @@ public abstract class
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true,
true);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
RaftServerTestUtil.waitAndCheckNewConf(cluster,
change.allPeersInNewConf, 0, null);
@@ -567,8 +572,8 @@ public abstract class
InstallSnapshotNotificationTests<CLUSTER extends MiniRaftC
RaftServerTestUtil.getLatestInstalledSnapshotIndex(follower));
}
- // Make sure each new peer got one snapshot notification.
- Assert.assertEquals(2, numSnapshotRequests.get());
+ // Make sure each new peer got at least one snapshot notification.
+ Assert.assertTrue(2 <= numSnapshotRequests.get());
} finally {
cluster.shutdown();
}
diff --git a/ratis-server/src/test/java/org/apache/ratis/RetryCacheTests.java
b/ratis-server/src/test/java/org/apache/ratis/RetryCacheTests.java
index 288aa71a9..18561ee65 100644
--- a/ratis-server/src/test/java/org/apache/ratis/RetryCacheTests.java
+++ b/ratis-server/src/test/java/org/apache/ratis/RetryCacheTests.java
@@ -28,6 +28,7 @@ import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.protocol.RaftPeerId;
import org.apache.ratis.server.RaftServer;
+import org.apache.ratis.server.impl.RaftServerTestUtil;
import org.apache.ratis.server.impl.RetryCacheTestUtil;
import org.apache.ratis.server.raftlog.RaftLog;
import org.apache.ratis.server.raftlog.RaftLogIOException;
@@ -139,7 +140,8 @@ public abstract class RetryCacheTests<CLUSTER extends
MiniRaftCluster>
RaftPeer[] allPeers = cluster.removePeers(2, true,
asList(change.newPeers)).allPeersInNewConf;
// trigger setConfiguration
- cluster.setConfiguration(allPeers);
+ RaftServerTestUtil.runWithMinorityPeers(cluster, Arrays.asList(allPeers),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
final RaftPeerId newLeaderId = JavaUtils.attemptRepeatedly(() -> {
final RaftPeerId id = RaftTestUtil.waitForLeader(cluster).getId();
diff --git
a/ratis-server/src/test/java/org/apache/ratis/server/impl/GroupManagementBaseTest.java
b/ratis-server/src/test/java/org/apache/ratis/server/impl/GroupManagementBaseTest.java
index f40afa088..311a2150d 100644
---
a/ratis-server/src/test/java/org/apache/ratis/server/impl/GroupManagementBaseTest.java
+++
b/ratis-server/src/test/java/org/apache/ratis/server/impl/GroupManagementBaseTest.java
@@ -329,7 +329,8 @@ public abstract class GroupManagementBaseTest extends
BaseTest {
}
LOG.info(chosen + ") setConfiguration: " +
cluster.printServers(groups[chosen].getGroupId()));
try (final RaftClient client = cluster.createClient(groups[chosen])) {
-
client.admin().setConfiguration(allPeers.toArray(RaftPeer.emptyArray()));
+ RaftServerTestUtil.runWithMinorityPeers(cluster, allPeers,
+ peers ->
client.admin().setConfiguration(peers.toArray(RaftPeer.emptyArray())));
}
Assert.assertNotNull(RaftTestUtil.waitForLeader(cluster));
diff --git
a/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftReconfigurationBaseTest.java
b/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftReconfigurationBaseTest.java
index b43c696c7..3b8e206de 100644
---
a/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftReconfigurationBaseTest.java
+++
b/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftReconfigurationBaseTest.java
@@ -61,6 +61,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static
org.apache.ratis.server.impl.RaftServerTestUtil.waitAndCheckNewConf;
+import static org.junit.Assert.assertThrows;
public abstract class RaftReconfigurationBaseTest<CLUSTER extends
MiniRaftCluster>
extends BaseTest
@@ -145,6 +146,58 @@ public abstract class RaftReconfigurationBaseTest<CLUSTER
extends MiniRaftCluste
});
}
+ /**
+ * Test leader election when changing cluster from single mode to HA mode.
+ */
+ @Test
+ public void testLeaderElectionWhenChangeFromSingleToHA() throws Exception {
+ runWithNewCluster(1, cluster -> {
+ RaftTestUtil.waitForLeader(cluster);
+
+ RaftGroupId groupId = cluster.getGroup().getGroupId();
+ RaftPeer curPeer = cluster.getGroup().getPeers().iterator().next();
+ RaftPeer newPeer = cluster.addNewPeers(1, true, true).newPeers[0];
+
+ RaftServerProxy leaderServer = cluster.getServer(curPeer.getId());
+
+ // Update leader conf to transitional single mode.
+ RaftConfigurationImpl oldNewConf = RaftConfigurationImpl.newBuilder()
+ .setOldConf(new PeerConfiguration(Arrays.asList(curPeer)))
+ .setConf(new PeerConfiguration(Arrays.asList(curPeer, newPeer)))
+ .setLogEntryIndex(Long.MAX_VALUE / 2)
+ .build();
+ Assert.assertTrue(oldNewConf.isSingleMode(curPeer.getId()));
+ RaftServerTestUtil.setRaftConf(leaderServer, groupId, oldNewConf);
+ try(RaftClient client = cluster.createClient()) {
+ client.admin().transferLeadership(null, leaderServer.getId(), 1000);
+ }
+
+ final RaftServer.Division newLeader =
RaftTestUtil.waitForLeader(cluster);
+ Assert.assertEquals(leaderServer.getId(), newLeader.getId());
+ Assert.assertEquals(oldNewConf, newLeader.getRaftConf());
+ });
+ }
+
+ @Test
+ public void testChangeMajority() throws Exception {
+ runWithNewCluster(1, cluster -> {
+ RaftTestUtil.waitForLeader(cluster);
+ final RaftPeerId leaderId = cluster.getLeader().getId();
+
+ try (final RaftClient client = cluster.createClient(leaderId)) {
+ final PeerChanges c1 = cluster.addNewPeers(2, true);
+
+ SetConfigurationRequest.Arguments arguments =
SetConfigurationRequest.Arguments.newBuilder()
+ .setServersInCurrentConf(cluster.getPeers())
+ .setServersInNewConf(c1.allPeersInNewConf)
+ .setMode(SetConfigurationRequest.Mode.COMPARE_AND_SET)
+ .build();
+ assertThrows("Expect change majority error.",
SetConfigurationException.class,
+ () -> client.admin().setConfiguration(arguments));
+ }
+ });
+ }
+
/**
* remove 2 peers (5 peers -> 3 peers), no leader change
*/
@@ -380,7 +433,17 @@ public abstract class RaftReconfigurationBaseTest<CLUSTER
extends MiniRaftCluste
@Test
public void testBootstrapReconfWithSingleNodeAddTwo() throws Exception {
// originally 1 peer, add 2 more
- runWithNewCluster(1, cluster -> runTestBootstrapReconf(2, true, cluster));
+ runWithNewCluster(1, cluster -> {
+ RaftTestUtil.waitForLeader(cluster);
+ final RaftPeerId leaderId = cluster.getLeader().getId();
+
+ try (final RaftClient client = cluster.createClient(leaderId)) {
+ final PeerChanges c1 = cluster.addNewPeers(2, true);
+
+ assertThrows("Expect change majority error.",
SetConfigurationException.class,
+ () -> client.admin().setConfiguration(c1.allPeersInNewConf));
+ }
+ });
}
@Test
diff --git
a/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftServerTestUtil.java
b/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftServerTestUtil.java
index 73482dcf8..2927ec349 100644
---
a/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftServerTestUtil.java
+++
b/ratis-server/src/test/java/org/apache/ratis/server/impl/RaftServerTestUtil.java
@@ -19,6 +19,7 @@ package org.apache.ratis.server.impl;
import org.apache.ratis.RaftTestUtil;
import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.protocol.RaftGroupId;
import org.apache.ratis.protocol.RaftGroupMemberId;
import org.apache.ratis.protocol.RaftPeer;
@@ -35,16 +36,22 @@ import org.apache.ratis.server.storage.RaftStorage;
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.Slf4jUtils;
import org.apache.ratis.util.TimeDuration;
+import org.apache.ratis.util.function.CheckedConsumer;
import org.junit.Assert;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
+import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RaftServerTestUtil {
@@ -135,6 +142,10 @@ public class RaftServerTestUtil {
return RaftConfigurationImpl.newBuilder().setConf(peers).build();
}
+ public static void setRaftConf(RaftServer proxy, RaftGroupId groupId,
RaftConfiguration conf) {
+ ((RaftServerImpl)getDivision(proxy, groupId)).getState().setRaftConf(conf);
+ }
+
public static RaftServerRpc getServerRpc(RaftServer.Division server) {
return ((RaftServerImpl)server).getRaftServer().getServerRpc();
}
@@ -196,4 +207,40 @@ public class RaftServerTestUtil {
public static boolean isHighestPriority(RaftConfiguration config, RaftPeerId
peerId) {
return ((RaftConfigurationImpl)config).isHighestPriority(peerId);
}
+
+ public static void runWithMinorityPeers(MiniRaftCluster cluster,
Collection<RaftPeer> peersInNewConf,
+ CheckedConsumer<Collection<RaftPeer>, IOException> consumer) throws
IOException {
+ Collection<RaftPeer> peers = parseMinorityPeers(cluster, peersInNewConf);
+ while (peers != null) {
+ consumer.accept(peers);
+ peers = parseMinorityPeers(cluster, peersInNewConf);
+ }
+ }
+
+ private static Collection<RaftPeer> parseMinorityPeers(MiniRaftCluster
cluster, Collection<RaftPeer> peersInNewConf) {
+ RaftConfigurationImpl conf = (RaftConfigurationImpl)
cluster.getLeader().getRaftConf();
+ Set<RaftPeer> peers = new HashSet<>(conf.getCurrentPeers());
+
+ // Add new peers to construct minority conf peers.
+ List<RaftPeer> peersToAdd = peersInNewConf.stream().filter(
+ peer -> !conf.containsInConf(peer.getId(),
RaftProtos.RaftPeerRole.FOLLOWER)).collect(Collectors.toList());
+ if (!peersToAdd.isEmpty()) {
+ for (RaftPeer peer : peersToAdd) {
+ if (peers.add(peer) && conf.changeMajority(peers)) {
+ peers.remove(peer);
+ break;
+ }
+ }
+ return peers;
+ }
+
+ // All new peers has been added. Handle the removed peers.
+ List<RaftPeer> peersToRemove = peers.stream().filter(peer ->
!peersInNewConf.contains(peer)).collect(Collectors.toList());
+ if (!peersToRemove.isEmpty()) {
+ return peersInNewConf;
+ }
+
+ // The peers in new conf are the same as current conf, return null.
+ return null;
+ }
}
diff --git
a/ratis-server/src/test/java/org/apache/ratis/statemachine/RaftSnapshotBaseTest.java
b/ratis-server/src/test/java/org/apache/ratis/statemachine/RaftSnapshotBaseTest.java
index 9cce49a64..fe1a97ddc 100644
---
a/ratis-server/src/test/java/org/apache/ratis/statemachine/RaftSnapshotBaseTest.java
+++
b/ratis-server/src/test/java/org/apache/ratis/statemachine/RaftSnapshotBaseTest.java
@@ -25,6 +25,7 @@ import static
org.apache.ratis.metrics.RatisMetrics.RATIS_APPLICATION_NAME_METRI
import org.apache.ratis.BaseTest;
import org.apache.ratis.metrics.LongCounter;
import org.apache.ratis.metrics.impl.DefaultTimekeeperImpl;
+import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.server.impl.MiniRaftCluster;
import org.apache.ratis.RaftTestUtil;
import org.apache.ratis.RaftTestUtil.SimpleMessage;
@@ -57,6 +58,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -239,7 +241,8 @@ public abstract class RaftSnapshotBaseTest extends BaseTest
{
MiniRaftCluster.PeerChanges change = cluster.addNewPeers(
newPeers, true, false);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
for (String newPeer : newPeers) {
final RaftServer.Division s =
cluster.getDivision(RaftPeerId.valueOf(newPeer));
@@ -301,7 +304,8 @@ public abstract class RaftSnapshotBaseTest extends BaseTest
{
MiniRaftCluster.PeerChanges change = cluster.addNewPeers(
newPeers, true, false);
// trigger setConfiguration
- cluster.setConfiguration(change.allPeersInNewConf);
+ RaftServerTestUtil.runWithMinorityPeers(cluster,
Arrays.asList(change.allPeersInNewConf),
+ peers ->
cluster.setConfiguration(peers.toArray(RaftPeer.emptyArray())));
for (String newPeer : newPeers) {
final RaftServer.Division s =
cluster.getDivision(RaftPeerId.valueOf(newPeer));
diff --git
a/ratis-test/src/test/java/org/apache/ratis/server/impl/TestRaftConfiguration.java
b/ratis-test/src/test/java/org/apache/ratis/server/impl/TestRaftConfiguration.java
index a5470787c..14e0030e6 100644
---
a/ratis-test/src/test/java/org/apache/ratis/server/impl/TestRaftConfiguration.java
+++
b/ratis-test/src/test/java/org/apache/ratis/server/impl/TestRaftConfiguration.java
@@ -22,13 +22,14 @@ import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.protocol.RaftPeerId;
import org.apache.ratis.server.RaftConfiguration;
-import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
public class TestRaftConfiguration extends BaseTest {
@Test
@@ -42,15 +43,15 @@ public class TestRaftConfiguration extends BaseTest {
RaftProtos.RaftPeerRole.FOLLOWER).size()]);
// First member should not have highest priority
- Assert.assertFalse(RaftServerTestUtil.isHighestPriority(config,
+ assertFalse(RaftServerTestUtil.isHighestPriority(config,
allRaftPeers[0].getId()));
// Last member should have highest priority
- Assert.assertTrue(RaftServerTestUtil.isHighestPriority(config,
+ assertTrue(RaftServerTestUtil.isHighestPriority(config,
allRaftPeers[allRaftPeers.length - 1].getId()));
// Should return false for non existent peer id
- Assert.assertFalse(RaftServerTestUtil.isHighestPriority(config,
RaftPeerId.valueOf("123456789")));
+ assertFalse(RaftServerTestUtil.isHighestPriority(config,
RaftPeerId.valueOf("123456789")));
}
private Collection<RaftPeer> raftPeersWithPriority(Integer... voters) {
@@ -58,4 +59,89 @@ public class TestRaftConfiguration extends BaseTest {
.map(id ->
RaftPeer.newBuilder().setPriority(id).setId(id.toString()).build())
.collect(Collectors.toSet());
}
+
+ @Test
+ public void testSingleMode() {
+ RaftConfigurationImpl config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1)))
+ .build();
+ assertTrue("Peer is in single mode.",
config.isSingleMode(RaftPeerId.valueOf("1")));
+
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(0, 1)))
+ .setOldConf(new PeerConfiguration(raftPeersWithPriority(0)))
+ .build();
+ assertTrue("Peer is in single mode.",
config.isSingleMode(RaftPeerId.valueOf("0")));
+ assertFalse("Peer is a new peer.",
config.isSingleMode(RaftPeerId.valueOf("1")));
+
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(0, 1)))
+ .build();
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("0")));
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("1")));
+
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(0, 1)))
+ .setOldConf(new PeerConfiguration(raftPeersWithPriority(2, 3)))
+ .build();
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("0")));
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("1")));
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("3")));
+ assertFalse("Peer is in ha mode.",
config.isSingleMode(RaftPeerId.valueOf("4")));
+ }
+
+ @Test
+ public void testChangeMajority() {
+ // Case 1: {1} --> {1, 2}.
+ RaftConfigurationImpl config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1)))
+ .build();
+ assertFalse("Change from single mode to ha mode is not considered as
changing majority.",
+ config.changeMajority(raftPeersWithPriority(1, 2)));
+
+ // Case 2: {1} --> {2}.
+ assertTrue(config.changeMajority(raftPeersWithPriority(2)));
+
+ // Case 3: {1, 2, 3} --> {1, 2, 4, 5}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3)))
+ .build();
+ assertTrue(config.changeMajority(raftPeersWithPriority(1, 2, 4, 5)));
+
+ // Case 4: {1, 2, 3} --> {1, 4, 5}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3)))
+ .build();
+ assertTrue(config.changeMajority(raftPeersWithPriority(1, 4, 5)));
+
+ // Case 5: {1, 2, 3} --> {1, 2, 3, 4, 5}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3)))
+ .build();
+ assertFalse(config.changeMajority(raftPeersWithPriority(1, 2, 3, 4, 5)));
+
+ // Case 6: {1, 2, 3, 4, 5} --> {1, 2}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3, 4, 5)))
+ .build();
+ assertFalse(config.changeMajority(raftPeersWithPriority(1, 2)));
+
+ // Case 7: {1, 2, 3, 4, 5} --> {1, 2, 3}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3, 4, 5)))
+ .build();
+ assertFalse(config.changeMajority(raftPeersWithPriority(1, 2, 3)));
+
+ // Case 8: {1, 2, 3} --> {1, 2, 3, 4}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3)))
+ .build();
+ assertFalse(config.changeMajority(raftPeersWithPriority(1, 2, 3, 4)));
+
+ // Case 9: {1, 2, 3, 4, 5} --> {1, 2, 3, 4, 6, 7}.
+ config = RaftConfigurationImpl.newBuilder()
+ .setConf(new PeerConfiguration(raftPeersWithPriority(1, 2, 3, 4, 5)))
+ .build();
+ assertFalse(config.changeMajority(raftPeersWithPriority(1, 2, 3, 4, 6,
7)));
+ }
}
\ No newline at end of file