package io.scalecube.cluster.membership;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import io.scalecube.cluster.ClusterConfig;
import io.scalecube.cluster.ClusterMath;
import io.scalecube.cluster.fdetector.FailureDetector;
import io.scalecube.cluster.gossip.GossipProtocol;
import io.scalecube.testlib.BaseTest;
import io.scalecube.transport.Address;
import io.scalecube.transport.Transport;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class MembershipProtocolTest extends BaseTest {
private static final int TEST_PING_INTERVAL = 200;
@Test
public void testInitialPhaseOk() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
try {
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
} finally {
stopAll(cm_a, cm_b, cm_c);
}
}
@Test
public void testNetworkPartitionThenRecovery() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
// Block traffic
a.networkEmulator().block(members);
b.networkEmulator().block(members);
c.networkEmulator().block(members);
try {
awaitSeconds(6);
assertTrusted(cm_a, a.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, b.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, c.address());
assertNoSuspected(cm_c);
a.networkEmulator().unblockAll();
b.networkEmulator().unblockAll();
c.networkEmulator().unblockAll();
awaitSeconds(6);
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
} finally {
stopAll(cm_a, cm_b, cm_c);
}
}
@Test
public void testMemberLostNetworkThenRecover() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
try {
awaitSeconds(1);
// Check all trusted
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
// Node b lost network
b.networkEmulator().block(Arrays.asList(a.address(), c.address()));
a.networkEmulator().block(b.address());
c.networkEmulator().block(b.address());
awaitSeconds(1);
// Check partition: {b}, {a, c}
assertTrusted(cm_a, a.address(), c.address());
assertSuspected(cm_a, b.address());
assertTrusted(cm_b, b.address());
assertSuspected(cm_b, a.address(), c.address());
assertTrusted(cm_c, a.address(), c.address());
assertSuspected(cm_c, b.address());
// Node b recover network
a.networkEmulator().unblockAll();
b.networkEmulator().unblockAll();
c.networkEmulator().unblockAll();
awaitSeconds(1);
// Check all trusted again
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
} finally {
stopAll(cm_a, cm_b, cm_c);
}
}
@Test
public void testDoublePartitionThenRecover() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
try {
awaitSeconds(1);
// Check all trusted
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
// Node b lost network
b.networkEmulator().block(Arrays.asList(a.address(), c.address()));
a.networkEmulator().block(b.address());
c.networkEmulator().block(b.address());
awaitSeconds(1);
// Check partition: {b}, {a, c}
assertTrusted(cm_a, a.address(), c.address());
assertSuspected(cm_a, b.address());
assertTrusted(cm_b, b.address());
assertSuspected(cm_b, a.address(), c.address());
assertTrusted(cm_c, a.address(), c.address());
assertSuspected(cm_c, b.address());
// Node a and c lost network
a.networkEmulator().block(c.address());
c.networkEmulator().block(a.address());
awaitSeconds(1);
// Check partition: {a}, {b}, {c}
assertTrusted(cm_a, a.address());
assertSuspected(cm_a, b.address(), c.address());
assertTrusted(cm_b, b.address());
assertSuspected(cm_b, a.address(), c.address());
assertTrusted(cm_c, c.address());
assertSuspected(cm_c, b.address(), a.address());
// Recover network
a.networkEmulator().unblockAll();
b.networkEmulator().unblockAll();
c.networkEmulator().unblockAll();
awaitSeconds(1);
// Check all trusted again
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
} finally {
stopAll(cm_a, cm_b, cm_c);
}
}
@Test
public void testNetworkDisabledThenRecovered() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
try {
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
a.networkEmulator().block(members);
b.networkEmulator().block(members);
c.networkEmulator().block(members);
awaitSeconds(1);
assertTrusted(cm_a, a.address());
assertSuspected(cm_a, b.address(), c.address());
assertTrusted(cm_b, b.address());
assertSuspected(cm_b,a.address(), c.address());
assertTrusted(cm_c, c.address());
assertSuspected(cm_c, a.address(), b.address());
a.networkEmulator().unblockAll();
b.networkEmulator().unblockAll();
c.networkEmulator().unblockAll();
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address(), c.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address());
assertNoSuspected(cm_c);
} finally {
stopAll(cm_a, cm_b, cm_c);
}
}
@Test
public void testLongNetworkPartitionNoRecovery() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
Transport d = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address(), d.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
MembershipProtocol cm_d = createMembership(d, members);
try {
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_b, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_c, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_d, a.address(), b.address(), c.address(), d.address());
a.networkEmulator().block(Arrays.asList(c.address(), d.address()));
b.networkEmulator().block(Arrays.asList(c.address(), d.address()));
c.networkEmulator().block(Arrays.asList(a.address(), b.address()));
d.networkEmulator().block(Arrays.asList(a.address(), b.address()));
awaitSeconds(2);
assertTrusted(cm_a, a.address(), b.address());
assertSuspected(cm_a, c.address(), d.address());
assertTrusted(cm_b, a.address(), b.address());
assertSuspected(cm_b, c.address(), d.address());
assertTrusted(cm_c, c.address(), d.address());
assertSuspected(cm_c, a.address(), b.address());
assertTrusted(cm_d, c.address(), d.address());
assertSuspected(cm_d, a.address(), b.address());
long suspicionTimeoutSec =
ClusterMath.suspicionTimeout(ClusterConfig.DEFAULT_SUSPICION_MULT, 4, TEST_PING_INTERVAL) / 1000;
awaitSeconds(suspicionTimeoutSec + 1); // > max suspect time
assertTrusted(cm_a, a.address(), b.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, c.address(), d.address());
assertNoSuspected(cm_c);
assertTrusted(cm_d, c.address(), d.address());
assertNoSuspected(cm_d);
} finally {
stopAll(cm_a, cm_b, cm_c, cm_d);
}
}
@Test
public void testRestartFailedMembers() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
Transport d = Transport.bindAwait(true);
List<Address> members = ImmutableList.of(a.address(), b.address(), c.address(), d.address());
MembershipProtocol cm_a = createMembership(a, members);
MembershipProtocol cm_b = createMembership(b, members);
MembershipProtocol cm_c = createMembership(c, members);
MembershipProtocol cm_d = createMembership(d, members);
MembershipProtocol cm_restartedC = null;
MembershipProtocol cm_restartedD = null;
try {
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_b, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_c, a.address(), b.address(), c.address(), d.address());
assertTrusted(cm_d, a.address(), b.address(), c.address(), d.address());
stop(cm_c);
stop(cm_d);
awaitSeconds(1);
assertTrusted(cm_a, a.address(), b.address());
assertSuspected(cm_a, c.address(), d.address());
assertTrusted(cm_b, a.address(), b.address());
assertSuspected(cm_b, c.address(), d.address());
long suspicionTimeoutSec =
ClusterMath.suspicionTimeout(ClusterConfig.DEFAULT_SUSPICION_MULT, 4, TEST_PING_INTERVAL) / 1000;
awaitSeconds(suspicionTimeoutSec + 1); // > max suspect time
assertTrusted(cm_a, a.address(), b.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address());
assertNoSuspected(cm_b);
c = Transport.bindAwait(true);
d = Transport.bindAwait(true);
cm_restartedC = createMembership(c, Arrays.asList(a.address(), b.address()));
cm_restartedD = createMembership(d, Arrays.asList(a.address(), b.address()));
awaitSeconds(1);
assertTrusted(cm_restartedC, a.address(), b.address(), c.address(), d.address());
assertNoSuspected(cm_restartedC);
assertTrusted(cm_restartedD, a.address(), b.address(), c.address(), d.address());
assertNoSuspected(cm_restartedD);
assertTrusted(cm_a, a.address(), b.address(), c.address(), d.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address(), d.address());
assertNoSuspected(cm_b);
} finally {
stopAll(cm_a, cm_b, cm_restartedC, cm_restartedD);
}
}
@Test
public void testLimitedSeedMembers() {
Transport a = Transport.bindAwait(true);
Transport b = Transport.bindAwait(true);
Transport c = Transport.bindAwait(true);
Transport d = Transport.bindAwait(true);
Transport e = Transport.bindAwait(true);
MembershipProtocol cm_a = createMembership(a, Collections.emptyList());
MembershipProtocol cm_b = createMembership(b, Collections.singletonList(a.address()));
MembershipProtocol cm_c = createMembership(c, Collections.singletonList(a.address()));
MembershipProtocol cm_d = createMembership(d, Collections.singletonList(b.address()));
MembershipProtocol cm_e = createMembership(e, Collections.singletonList(b.address()));
try {
awaitSeconds(3);
assertTrusted(cm_a, a.address(), b.address(), c.address(), d.address(), e.address());
assertNoSuspected(cm_a);
assertTrusted(cm_b, a.address(), b.address(), c.address(), d.address(), e.address());
assertNoSuspected(cm_b);
assertTrusted(cm_c, a.address(), b.address(), c.address(), d.address(), e.address());
assertNoSuspected(cm_c);
assertTrusted(cm_d, a.address(), b.address(), c.address(), d.address(), e.address());
assertNoSuspected(cm_d);
assertTrusted(cm_e, a.address(), b.address(), c.address(), d.address(), e.address());
assertNoSuspected(cm_e);
} finally {
stopAll(cm_a, cm_b, cm_c, cm_d, cm_e);
}
}
private void awaitSeconds(long seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
private MembershipProtocol createMembership(Transport transport, List<Address> seedAddresses) {
// Create faster config for local testing
ClusterConfig config = ClusterConfig.builder()
.seedMembers(seedAddresses)
.syncInterval(2000)
.syncTimeout(1000)
.pingInterval(TEST_PING_INTERVAL)
.pingTimeout(100)
.build();
// Create components
MembershipProtocol membership = new MembershipProtocol(transport, config);
FailureDetector failureDetector = new FailureDetector(transport, membership, config);
GossipProtocol gossipProtocol = new GossipProtocol(transport, membership, config);
membership.setGossipProtocol(gossipProtocol);
membership.setFailureDetector(failureDetector);
try {
failureDetector.start();
gossipProtocol.start();
membership.start().get();
} catch (Exception ex) {
Throwables.propagate(ex);
}
return membership;
}
private void stopAll(MembershipProtocol... memberships) {
for (MembershipProtocol membership : memberships) {
if (membership != null) {
stop(membership);
}
}
}
private void stop(MembershipProtocol membership) {
membership.stop();
membership.getGossipProtocol().stop();
membership.getFailureDetector().stop();
Transport transport = membership.getTransport();
CompletableFuture<Void> close = new CompletableFuture<>();
transport.stop(close);
try {
close.get(1, TimeUnit.SECONDS);
} catch (Exception ignore) {
// ignore
}
}
private void assertTrusted(MembershipProtocol membership, Address... expected) {
List<Address> actual = getAddressesWithStatus(membership, MemberStatus.ALIVE);
assertEquals("Expected " + expected.length + " trusted members " + Arrays.toString(expected)
+ ", but actual: " + actual, expected.length, actual.size());
for (Address member : expected) {
assertTrue("Expected to trust " + member + ", but actual: " + actual, actual.contains(member));
}
}
private void assertSuspected(MembershipProtocol membership, Address... expected) {
List<Address> actual = getAddressesWithStatus(membership, MemberStatus.SUSPECT);
assertEquals("Expected " + expected.length + " suspect members " + Arrays.toString(expected)
+ ", but actual: " + actual, expected.length, actual.size());
for (Address member : expected) {
assertTrue("Expected to suspect " + member + ", but actual: " + actual, actual.contains(member));
}
}
private void assertNoSuspected(MembershipProtocol membership) {
List<Address> actual = getAddressesWithStatus(membership, MemberStatus.SUSPECT);
assertEquals("Expected no suspected, but actual: " + actual, 0, actual.size());
}
private List<Address> getAddressesWithStatus(MembershipProtocol membership, MemberStatus status) {
return membership.getMembershipRecords().stream()
.filter(member -> member.status() == status)
.map(MembershipRecord::address)
.collect(Collectors.toList());
}
}