blob: 0fb00d07c8603ba3ef845d4f9490b52fea4ffd4f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.distributed.internal.membership.gms.membership;
import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.internal.verification.Times;
import org.mockito.verification.Timeout;
import org.apache.geode.distributed.internal.membership.api.Authenticator;
import org.apache.geode.distributed.internal.membership.api.MemberDataBuilder;
import org.apache.geode.distributed.internal.membership.api.MemberIdentifier;
import org.apache.geode.distributed.internal.membership.api.MemberIdentifierFactoryImpl;
import org.apache.geode.distributed.internal.membership.api.MemberStartupException;
import org.apache.geode.distributed.internal.membership.api.MembershipConfig;
import org.apache.geode.distributed.internal.membership.api.MembershipConfigurationException;
import org.apache.geode.distributed.internal.membership.gms.GMSMembershipView;
import org.apache.geode.distributed.internal.membership.gms.GMSUtil;
import org.apache.geode.distributed.internal.membership.gms.MemberIdentifierImpl;
import org.apache.geode.distributed.internal.membership.gms.Services;
import org.apache.geode.distributed.internal.membership.gms.Services.Stopper;
import org.apache.geode.distributed.internal.membership.gms.interfaces.HealthMonitor;
import org.apache.geode.distributed.internal.membership.gms.interfaces.Locator;
import org.apache.geode.distributed.internal.membership.gms.interfaces.Manager;
import org.apache.geode.distributed.internal.membership.gms.interfaces.Messenger;
import org.apache.geode.distributed.internal.membership.gms.locator.FindCoordinatorRequest;
import org.apache.geode.distributed.internal.membership.gms.locator.FindCoordinatorResponse;
import org.apache.geode.distributed.internal.membership.gms.membership.GMSJoinLeave.SearchState;
import org.apache.geode.distributed.internal.membership.gms.membership.GMSJoinLeave.ViewCreator;
import org.apache.geode.distributed.internal.membership.gms.membership.GMSJoinLeave.ViewReplyProcessor;
import org.apache.geode.distributed.internal.membership.gms.messages.InstallViewMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.JoinRequestMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.JoinResponseMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.LeaveRequestMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.NetworkPartitionMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.RemoveMemberMessage;
import org.apache.geode.distributed.internal.membership.gms.messages.ViewAckMessage;
import org.apache.geode.distributed.internal.membership.gms.util.MemberIdentifierUtil;
import org.apache.geode.distributed.internal.tcpserver.HostAndPort;
import org.apache.geode.distributed.internal.tcpserver.TcpClient;
import org.apache.geode.internal.serialization.KnownVersion;
import org.apache.geode.test.junit.categories.MembershipTest;
@Category({MembershipTest.class})
public class GMSJoinLeaveJUnitTest {
private Services services;
private MembershipConfig mockConfig;
private Authenticator authenticator;
private HealthMonitor healthMonitor;
private MemberIdentifier gmsJoinLeaveMemberId;
private MemberIdentifier[] mockMembers;
private MemberIdentifier mockOldMember;
private Properties credentials = new Properties();
private Messenger messenger;
private GMSJoinLeave gmsJoinLeave;
private Manager manager;
private Stopper stopper;
private TestLocator testLocator;
private MemberIdentifier removeMember = null;
private MemberIdentifier leaveMember = null;
private TcpClient locatorClient;
public void initMocks() throws Exception {
initMocks(false);
}
public void initMocks(boolean enableNetworkPartition) throws Exception {
initMocks(enableNetworkPartition, false);
}
public void initMocks(boolean enableNetworkPartition, boolean useTestGMSJoinLeave)
throws Exception {
String locator = "localhost[12345]";
initMocks(enableNetworkPartition, useTestGMSJoinLeave, locator, locator);
}
public void initMocks(boolean enableNetworkPartition, boolean useTestGMSJoinLeave,
String locators, String startLocator)
throws Exception {
mockConfig = mock(MembershipConfig.class);
when(mockConfig.isNetworkPartitionDetectionEnabled()).thenReturn(enableNetworkPartition);
when(mockConfig.getSecurityUDPDHAlgo()).thenReturn("");
when(mockConfig.getStartLocator()).thenReturn(startLocator);
when(mockConfig.getLocators()).thenReturn(locators);
when(mockConfig.getMcastPort()).thenReturn(0);
when(mockConfig.getMemberTimeout()).thenReturn(2000L);
authenticator = mock(Authenticator.class);
stopper = mock(Stopper.class);
when(stopper.isCancelInProgress()).thenReturn(false);
manager = mock(Manager.class);
healthMonitor = mock(HealthMonitor.class);
when(healthMonitor.getFailureDetectionPort()).thenReturn(Integer.valueOf(-1));
services = mock(Services.class);
when(services.getAuthenticator()).thenReturn(authenticator);
when(services.getConfig()).thenReturn(mockConfig);
when(services.getCancelCriterion()).thenReturn(stopper);
when(services.getManager()).thenReturn(manager);
when(services.getHealthMonitor()).thenReturn(healthMonitor);
when(services.getMemberFactory())
.thenReturn(new MemberIdentifierFactoryImpl());
gmsJoinLeaveMemberId = services.getMemberFactory().create(
MemberDataBuilder.newBuilderForLocalHost("localhost")
.setMembershipPort(8887).build());
messenger = mock(Messenger.class);
when(messenger.getMemberID()).thenReturn(gmsJoinLeaveMemberId);
when(services.getMessenger()).thenReturn(messenger);
testLocator = new TestLocator();
when(services.getLocator()).thenReturn(testLocator);
Timer t = new Timer(true);
when(services.getTimer()).thenReturn(t);
mockMembers = new MemberIdentifier[4];
for (int i = 0; i < mockMembers.length; i++) {
mockMembers[i] = services.getMemberFactory().create(
MemberDataBuilder.newBuilderForLocalHost("localhost")
.setMembershipPort(8888 + i).build());
}
mockOldMember = services.getMemberFactory().create(
MemberDataBuilder.newBuilderForLocalHost("localhost")
.setMembershipPort(8700).build());
((MemberIdentifierImpl) mockOldMember).setVersionForTest(KnownVersion.OLDEST);
locatorClient = mock(TcpClient.class);
if (useTestGMSJoinLeave) {
gmsJoinLeave = new GMSJoinLeaveTest(locatorClient);
} else {
gmsJoinLeave = new GMSJoinLeave(locatorClient);
}
gmsJoinLeave.init(services);
gmsJoinLeave.start();
gmsJoinLeave.started();
gmsJoinLeave.setLocalAddress(gmsJoinLeaveMemberId);
}
@After
public void tearDown() throws Exception {
if (gmsJoinLeave != null) {
gmsJoinLeave.stop();
gmsJoinLeave.stopped();
}
}
static class TestLocator implements Locator {
boolean isCoordinator;
@Override
public void installView(GMSMembershipView v) {}
@Override
public void setIsCoordinator(boolean isCoordinator) {
this.isCoordinator = isCoordinator;
}
public boolean isCoordinator() {
return isCoordinator;
}
}
@Test
public void testFindCoordinatorPausesWhenLocatorWaitTimeIsSet() throws Exception {
initMocks(false);
when(mockConfig.getLocatorWaitTime()).thenReturn(15000);
when(locatorClient.requestToServer(isA(HostAndPort.class),
isA(FindCoordinatorRequest.class), anyInt(), anyBoolean()))
.thenThrow(new IOException("Connection refused"));
// interrupt this thread so that findCoordinator() won't keep looping
// and will throw an exception when going to pause
Thread.currentThread().interrupt();
assertThatThrownBy(() -> gmsJoinLeave.findCoordinator())
.isInstanceOf(MemberStartupException.class)
.hasMessageContaining("Interrupted while trying to contact locators");
assertThat(Thread.currentThread().interrupted()).isTrue();
verify(locatorClient, times(1)).requestToServer(isA(HostAndPort.class),
isA(FindCoordinatorRequest.class), anyInt(), anyBoolean());
}
@Test
public void testFindCoordinatorInView() throws Exception {
initMocks();
int viewId = 1;
List<MemberIdentifier> mbrs = new LinkedList<>();
mbrs.add(mockMembers[0]);
mbrs.add(mockMembers[1]);
mbrs.add(mockMembers[2]);
// prepare the view
GMSMembershipView netView = new GMSMembershipView(mockMembers[0], viewId, mbrs);
SearchState state = gmsJoinLeave.searchState;
state.view = netView;
state.viewId = netView.getViewId();
MemberIdentifier coordinator = mockMembers[2];
coordinator.setVmViewId(viewId);
// already tried joining using members 0 and 1
Set<MemberIdentifier> set = new HashSet<>();
mockMembers[0].setVmViewId(viewId - 1);
set.add(mockMembers[0]);
mockMembers[1].setVmViewId(viewId - 1);
set.add(mockMembers[1]);
state.alreadyTried = set;
state.hasContactedAJoinedLocator = true;
// simulate a response being received
MemberIdentifier sender = mockMembers[2];
FindCoordinatorResponse resp = new FindCoordinatorResponse(coordinator, sender, null, 0);
gmsJoinLeave.processMessage(resp);
// tell GMSJoinLeave that a unit test is running so it won't clear the
// responses collection
gmsJoinLeave.unitTesting.add("findCoordinatorFromView");
// now for the test
boolean result = gmsJoinLeave.findCoordinatorFromView();
assertTrue("should have found coordinator " + mockMembers[2], result);
assertTrue("should have found " + coordinator + " but found " + state.possibleCoordinator,
state.possibleCoordinator == coordinator);
}
@Test
public void testProcessJoinMessageRejectOldMemberVersion() throws Exception {
initMocks();
gmsJoinLeave.processMessage(new JoinRequestMessage(mockOldMember, mockOldMember, null, -1, 0));
assertTrue("JoinRequest should not have been added to view request",
gmsJoinLeave.getViewRequests().size() == 0);
verify(messenger).send(isA(JoinResponseMessage.class));
}
@Test
public void testViewWithoutMemberInitiatesForcedDisconnect() throws Exception {
initMocks();
becomeCoordinatorForTest(gmsJoinLeave);
List<MemberIdentifier> members = Arrays.asList(mockMembers);
GMSMembershipView v = new GMSMembershipView(mockMembers[0], 2, members);
InstallViewMessage message = getInstallViewMessage(v, null, false);
gmsJoinLeave.processMessage(message);
verify(manager).forceDisconnect(isA(String.class));
}
@Test
public void testProcessJoinMessageWithBadAuthentication() throws Exception {
initMocks();
when(authenticator.authenticate(mockMembers[0], credentials))
.thenThrow(new SecurityException("we want to fail auth here"));
gmsJoinLeave
.processMessage(new JoinRequestMessage(mockMembers[0], mockMembers[0], credentials, -1, 0));
assertTrue("JoinRequest should not have been added to view request",
gmsJoinLeave.getViewRequests().size() == 0);
verify(messenger).send(isA(JoinResponseMessage.class));
}
@Test
public void testProcessJoinMessageWithAuthenticationButNullCredentials() throws Exception {
initMocks();
when(authenticator.authenticate(mockMembers[0], null))
.thenThrow(new SecurityException("we want to fail auth here"));
gmsJoinLeave
.processMessage(new JoinRequestMessage(mockMembers[0], mockMembers[0], null, -1, 0));
assertTrue("JoinRequest should not have been added to view request",
gmsJoinLeave.getViewRequests().size() == 0);
verify(messenger).send(isA(JoinResponseMessage.class));
}
// This test does not test the actual join process but rather that the join response gets logged
@Test
public void testProcessJoinResponseIsRecorded() throws Exception {
initMocks();
when(authenticator.authenticate(mockMembers[0], null))
.thenThrow(new SecurityException("we want to fail auth here"));
JoinResponseMessage[] joinResponse = gmsJoinLeave.getJoinResponseMessage();
JoinResponseMessage jrm = new JoinResponseMessage(mockMembers[0], new byte[9], 233);
gmsJoinLeave.processMessage(jrm);
// this should NOT logs, this is just to inform member succesful joining
Assert.assertEquals(null, joinResponse[0]);
jrm = new JoinResponseMessage("rejected...", 0);
gmsJoinLeave.processMessage(jrm);
// this should log..
Assert.assertEquals(jrm, joinResponse[0]);
}
/**
* prepares and install a view
*
*/
private void prepareAndInstallView(MemberIdentifier coordinator,
List<MemberIdentifier> members) throws IOException {
int viewId = 1;
// prepare the view
GMSMembershipView<MemberIdentifier> netView =
new GMSMembershipView(coordinator, viewId, members);
for (MemberIdentifier member : netView.getMembers()) {
netView.setPublicKey(member, member.toString());
}
InstallViewMessage installViewMessage = getInstallViewMessage(netView, credentials, true);
gmsJoinLeave.processMessage(installViewMessage);
verify(messenger).send(isA(ViewAckMessage.class));
// install the view
installViewMessage = getInstallViewMessage(netView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
Assert.assertEquals(netView, gmsJoinLeave.getView());
}
private List<MemberIdentifier> createMemberList(MemberIdentifier... members) {
List<MemberIdentifier> memberList =
new ArrayList<MemberIdentifier>(members.length);
for (MemberIdentifier member : members) {
memberList.add(member);
}
return memberList;
}
@Test
public void testRemoveMember() throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
gmsJoinLeave.remove(gmsJoinLeaveMemberId, "removing for test");
verify(messenger, timeout(2000).atLeastOnce()).send(isA(RemoveMemberMessage.class));
}
@Test
public void testIsMemberLeaving() throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0],
createMemberList(mockMembers[0], mockMembers[1], gmsJoinLeaveMemberId));
assertFalse(gmsJoinLeave.isMemberLeaving(mockMembers[0]));
assertFalse(gmsJoinLeave.isMemberLeaving(mockMembers[1]));
gmsJoinLeave.remove(mockMembers[0], "removing for test");
verify(messenger, timeout(2000).atLeastOnce()).send(isA(RemoveMemberMessage.class));
assertTrue(gmsJoinLeave.isMemberLeaving(mockMembers[0]));
LeaveRequestMessage msg =
new LeaveRequestMessage(gmsJoinLeave.getMemberID(), mockMembers[1], "leaving for test");
msg.setSender(mockMembers[1]);
gmsJoinLeave.processMessage(msg);
assertTrue(gmsJoinLeave.isMemberLeaving(mockMembers[1]));
}
@Test
public void testRemoveAndLeaveIsNotACrash() throws Exception {
// simultaneous leave & remove requests for a member
// should not result in it's being seen as a crashed member
initMocks();
final int viewInstallationTime = 15000;
when(healthMonitor.checkIfAvailable(isA(MemberIdentifier.class), isA(String.class),
isA(Boolean.class))).thenReturn(true);
gmsJoinLeave.delayViewCreationForTest(5000); // ensures multiple requests are queued for a view
// change
becomeCoordinatorForTest(gmsJoinLeave);
await()
.until(() -> gmsJoinLeave.getView() != null);
GMSMembershipView oldView = gmsJoinLeave.getView();
GMSMembershipView newView = new GMSMembershipView(oldView, oldView.getViewId() + 1);
newView.add(mockMembers[1]);
newView.add(mockMembers[2]);
gmsJoinLeave.installView(newView);
gmsJoinLeave.memberShutdown(mockMembers[1], "shutting down for test");
gmsJoinLeave.remove(mockMembers[1], "removing for test");
await()
.until(() -> gmsJoinLeave.getView().getViewId() > newView.getViewId());
assertFalse(gmsJoinLeave.getView().getCrashedMembers().contains(mockMembers[1]));
}
@Test
public void multipleLocatorsWithSameAddressAreCanonicalized() throws Exception {
List<HostAndPort> locators = GMSUtil.parseLocators(
"localhost[1234],localhost[1234],localhost[1234]", (InetAddress) null);
assertThat(locators.size()).isEqualTo(1);
}
@Test
public void testRejectOlderView() throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
List<MemberIdentifier> mbrs = new LinkedList<>();
mbrs.add(mockMembers[0]);
mbrs.add(mockMembers[1]);
// try to install an older view where viewId < currentView.viewId
GMSMembershipView olderNetView = new GMSMembershipView(mockMembers[0], 0, mbrs);
InstallViewMessage installViewMessage = getInstallViewMessage(olderNetView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
Assert.assertNotEquals(gmsJoinLeave.getView(), olderNetView);
}
@Test
public void testForceDisconnectedFromNewView() throws Exception {
initMocks(true);// enabledNetworkPartition;
Manager mockManager = mock(Manager.class);
when(services.getManager()).thenReturn(mockManager);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
int viewId = 2;
List<MemberIdentifier> mbrs = new LinkedList<>();
mbrs.add(mockMembers[1]);
mbrs.add(mockMembers[2]);
mbrs.add(mockMembers[3]);
// install the view
GMSMembershipView netView = new GMSMembershipView(mockMembers[0], viewId, mbrs);
InstallViewMessage installViewMessage = getInstallViewMessage(netView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
Assert.assertNotEquals(netView, gmsJoinLeave.getView());
verify(mockManager).forceDisconnect(isA(String.class));
}
@Test
public void testNonMemberCantRemoveMember() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
// test that a non-member can't remove another member
RemoveMemberMessage msg = new RemoveMemberMessage(mockMembers[0], mockMembers[1], reason);
msg.setSender(MemberIdentifierUtil.createMemberID(9000));
gmsJoinLeave.processMessage(msg);
assertTrue("RemoveMemberMessage should not have been added to view requests",
gmsJoinLeave.getViewRequests().size() == 0);
}
@Test
public void testDuplicateLeaveRequestDoesNotCauseNewView() throws Exception {
String reason = "testing";
initMocks();
gmsJoinLeave.unitTesting.add("noRandomViewChange");
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
becomeCoordinatorForTest(gmsJoinLeave);
LeaveRequestMessage msg =
new LeaveRequestMessage(gmsJoinLeave.getMemberID(), mockMembers[0], reason);
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
msg = new LeaveRequestMessage(gmsJoinLeave.getMemberID(), mockMembers[0], reason);
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
waitForViewAndNoRequestsInProgress(7);
GMSMembershipView view = gmsJoinLeave.getView();
assertTrue("expected member to be removed: " + mockMembers[0] + "; view: " + view,
!view.contains(mockMembers[0]));
assertTrue("expected member to be in shutdownMembers collection: " + mockMembers[0] + "; view: "
+ view, view.getShutdownMembers().contains(mockMembers[0]));
}
@Test
public void testDuplicateRemoveRequestDoesNotCauseNewView() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
gmsJoinLeave.getView().add(mockMembers[1]);
gmsJoinLeave.unitTesting.add("noRandomViewChange");
becomeCoordinatorForTest(gmsJoinLeave);
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeave.getMemberID(), mockMembers[0], reason);
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
msg = new RemoveMemberMessage(gmsJoinLeave.getMemberID(), mockMembers[0], reason);
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
waitForViewAndNoRequestsInProgress(7);
GMSMembershipView view = gmsJoinLeave.getView();
assertTrue("expected member to be removed: " + mockMembers[0] + "; view: " + view,
!view.contains(mockMembers[0]));
assertTrue(
"expected member to be in crashedMembers collection: " + mockMembers[0] + "; view: " + view,
view.getCrashedMembers().contains(mockMembers[0]));
}
@Test
public void testDuplicateJoinRequestDoesNotCauseNewView() throws Exception {
initMocks();
when(healthMonitor.checkIfAvailable(isA(MemberIdentifier.class), isA(String.class),
isA(Boolean.class))).thenReturn(true);
gmsJoinLeave.unitTesting.add("noRandomViewChange");
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
gmsJoinLeave.getView().add(mockMembers[1]);
becomeCoordinatorForTest(gmsJoinLeave);
JoinRequestMessage msg =
new JoinRequestMessage(gmsJoinLeaveMemberId, mockMembers[2], null, -1, 0);
msg.setSender(mockMembers[2]);
gmsJoinLeave.processMessage(msg);
msg = new JoinRequestMessage(gmsJoinLeaveMemberId, mockMembers[2], null, -1, 0);
msg.setSender(mockMembers[2]);
gmsJoinLeave.processMessage(msg);
waitForViewAndNoRequestsInProgress(7);
GMSMembershipView view = gmsJoinLeave.getView();
assertTrue("expected member to be added: " + mockMembers[2] + "; view: " + view,
view.contains(mockMembers[2]));
List<MemberIdentifier> members = view.getMembers();
int occurrences = 0;
for (MemberIdentifier mbr : members) {
if (mbr.equals(mockMembers[2])) {
occurrences += 1;
}
}
assertTrue("expected member to only be in the view once: " + mockMembers[2] + "; view: " + view,
occurrences == 1);
verify(healthMonitor, times(5)).checkIfAvailable(isA(MemberIdentifier.class),
isA(String.class), isA(Boolean.class));
}
private void waitForViewAndNoRequestsInProgress(int viewId) throws InterruptedException {
// wait for the view processing thread to collect and process the requests
int sleeps = 0;
while (!gmsJoinLeave.isStopping() && !gmsJoinLeave.getViewCreator().isWaiting()
&& (!gmsJoinLeave.getViewRequests().isEmpty()
|| gmsJoinLeave.getView().getViewId() != viewId)) {
if (sleeps++ > 20) {
throw new RuntimeException("timeout waiting for view #" + viewId + " current view: "
+ gmsJoinLeave.getView() + "; view requests: " + gmsJoinLeave.getViewRequests());
}
Thread.sleep(1000);
}
}
@Test
public void testRemoveMessageForRogueCausesImmediateRemovalMessageToRogue() throws Exception {
initMocks();
gmsJoinLeave.currentView = createView();
gmsJoinLeave.isCoordinator = true;
RemoveMemberMessage msg = new RemoveMemberMessage(gmsJoinLeaveMemberId,
MemberIdentifierUtil.createMemberID(10000), "removing for test");
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
verify(messenger).send(isA(RemoveMemberMessage.class));
}
@Test
public void testRemoveRequestCausesForcedDisconnectInRogue() throws Exception {
initMocks();
// gmsJoinLeave mistakenly uses an old viewID when joining, making it a rogue member
gmsJoinLeaveMemberId.setVmViewId(-1);
MemberIdentifier previousMemberId = services.getMemberFactory().create(
MemberDataBuilder.newBuilder(gmsJoinLeaveMemberId.getInetAddress(),
gmsJoinLeaveMemberId.getHostName())
.setMembershipPort(gmsJoinLeaveMemberId.getMembershipPort())
.build());
previousMemberId.setVmViewId(0);
previousMemberId.setUUID(gmsJoinLeaveMemberId.getUUID());
GMSMembershipView view = new GMSMembershipView(mockMembers[0], 1,
createMemberList(mockMembers[0], previousMemberId, mockMembers[1]));
InstallViewMessage viewMessage = new InstallViewMessage(view, 0, false);
viewMessage.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(viewMessage);
assertEquals(0, gmsJoinLeaveMemberId.getVmViewId());
// a RemoveMember message should cause it to force-disconnect
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeaveMemberId, gmsJoinLeaveMemberId, "removing for test");
msg.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(msg);
verify(manager).forceDisconnect("removing for test");
}
@Test
public void testRemoveCausesForcedDisconnect() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
gmsJoinLeave.getView().add(mockMembers[1]);
RemoveMemberMessage msg =
new RemoveMemberMessage(mockMembers[0], gmsJoinLeave.getMemberID(), reason);
msg.setSender(mockMembers[1]);
gmsJoinLeave.processMessage(msg);
verify(manager).forceDisconnect(reason);
}
@Test
public void testLeaveCausesForcedDisconnect() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
gmsJoinLeave.getView().add(mockMembers[1]);
LeaveRequestMessage msg =
new LeaveRequestMessage(gmsJoinLeave.getMemberID(), gmsJoinLeave.getMemberID(), reason);
msg.setSender(mockMembers[1]);
gmsJoinLeave.processMessage(msg);
verify(manager).forceDisconnect(reason);
}
@Test
public void testLeaveOfNonMemberIsNoOp() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
mockMembers[1].setVmViewId(gmsJoinLeave.getView().getViewId() - 1);
LeaveRequestMessage msg =
new LeaveRequestMessage(gmsJoinLeave.getMemberID(), mockMembers[1], reason);
msg.setSender(mockMembers[1]);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected leave request from non-member to be ignored",
gmsJoinLeave.getViewRequests().isEmpty());
}
@Test
public void testBecomeCoordinatorOnStartup() throws Exception {
initMocks();
becomeCoordinatorForTest(gmsJoinLeave);
await().until(() -> gmsJoinLeave.isCoordinator());
}
@Test
public void testBecomeCoordinator() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView view = gmsJoinLeave.getView();
view.add(gmsJoinLeaveMemberId);
MemberIdentifier creator = view.getCreator();
LeaveRequestMessage msg = new LeaveRequestMessage(creator, creator, reason);
msg.setSender(creator);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected becomeCoordinator to be invoked", gmsJoinLeave.isCoordinator());
}
@Test
public void testBecomeCoordinatorThroughRemove() throws Exception {
String reason = "testing";
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView view = gmsJoinLeave.getView();
view.add(gmsJoinLeaveMemberId);
MemberIdentifier creator = view.getCreator();
RemoveMemberMessage msg = new RemoveMemberMessage(creator, creator, reason);
msg.setSender(creator);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected becomeCoordinator to be invoked", gmsJoinLeave.isCoordinator());
}
/**
* Given a view with [A, B, C, D, E] where C is coordinator, A failed availability checks and
* C shuts down we should see B become the coordinator.
*/
@Test
public void testBecomeCoordinatorThroughShutdownWhenOlderMemberCrashed() throws Exception {
initMocks();
MemberIdentifier A = mockMembers[0],
B = gmsJoinLeaveMemberId,
C = mockMembers[1],
D = mockMembers[2],
E = mockMembers[3];
prepareAndInstallView(C, createMemberList(A, B, C, D, E));
LeaveRequestMessage msg = new LeaveRequestMessage(B, C, "leaving for test");
msg.setSender(C);
gmsJoinLeave.processMessage(msg);
RemoveMemberMessage removeMemberMessage = new RemoveMemberMessage(B, A, "removing for test");
removeMemberMessage.setSender(B);
gmsJoinLeave.processMessage(removeMemberMessage);
assertTrue("Expected becomeCoordinator to be invoked", gmsJoinLeave.isCoordinator());
}
/**
* Given a view with [A, B, C, D] where C is coordinator, a failed availability check on A
* and a pending join request from E show that B becomes coordinator when C sends it a
* Leave request and that E is allowed into the system and retains its view ID.
* See GEODE-6570
*/
@Test
public void testBecomeCoordinatorAndAcceptMemberWithViewID() throws Exception {
initMocks();
MemberIdentifier A = mockMembers[0],
B = gmsJoinLeaveMemberId,
C = mockMembers[1],
D = mockMembers[2],
E = mockMembers[3];
prepareAndInstallView(C, createMemberList(A, B, C, D));
// have the Messenger acknowledge all membership view messages so no-one is kicked out for
// failure to respond
when(messenger.send(isA(InstallViewMessage.class), isA(GMSMembershipView.class)))
.thenAnswer((request) -> {
InstallViewMessage<MemberIdentifier> installViewMessage = request.getArgument(0);
for (MemberIdentifier recipient : installViewMessage.getRecipients()) {
ViewAckMessage viewAckMessage =
new ViewAckMessage(gmsJoinLeaveMemberId, installViewMessage.getView().getViewId(),
installViewMessage.isPreparing());
viewAckMessage.setSender(recipient);
gmsJoinLeave.processMessage(viewAckMessage);
}
return null;
});
E.setVmViewId(2);
gmsJoinLeave.recordViewRequest(new LeaveRequestMessage(B, C, "removing for test"));
gmsJoinLeave.processMessage(new JoinRequestMessage(B, E, null, 1, 1));
RemoveMemberMessage msg = new RemoveMemberMessage(B, A, "crashed for test");
msg.setSender(D);
gmsJoinLeave.processMessage(msg);
await().until(() -> gmsJoinLeave.isCoordinator() && gmsJoinLeave.getViewRequests().isEmpty());
// E should have joined and retained its view ID of 2
await().until(() -> gmsJoinLeave.getView().contains(E));
assertEquals(2, E.getVmViewId());
}
@Test
public void testBecomeCoordinatorThroughViewChange() throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView oldView = gmsJoinLeave.getView();
oldView.add(gmsJoinLeaveMemberId);
GMSMembershipView view = new GMSMembershipView(oldView, oldView.getViewId() + 1);
MemberIdentifier creator = view.getCreator();
view.remove(creator);
InstallViewMessage msg = getInstallViewMessage(view, creator, false);
msg.setSender(creator);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected it to become coordinator", gmsJoinLeave.isCoordinator());
}
@Test
public void testBecomeCoordinatorThroughViewChangeWhenCoordinatorIsShuttingDown()
throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView oldView = gmsJoinLeave.getView();
oldView.add(gmsJoinLeaveMemberId);
GMSMembershipView view = new GMSMembershipView(oldView, oldView.getViewId() + 1);
MemberIdentifier creator = view.getCreator();
LeaveRequestMessage leaveRequestMessage =
new LeaveRequestMessage(gmsJoinLeaveMemberId, mockMembers[0], "leaving for test");
gmsJoinLeave.processMessage(leaveRequestMessage);
assertTrue(gmsJoinLeave.isCoordinator());
InstallViewMessage msg = getInstallViewMessage(view, creator, false);
msg.setSender(creator);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected it to remain coordinator", gmsJoinLeave.isCoordinator());
}
@Test
public void testBecomeParticipantThroughViewChange() throws Exception {
initMocks();
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView oldView = gmsJoinLeave.getView();
oldView.add(gmsJoinLeaveMemberId);
MemberIdentifier creator = oldView.getCreator();
becomeCoordinatorForTest(gmsJoinLeave);
GMSMembershipView view = new GMSMembershipView(2, gmsJoinLeave.getView().getViewId() + 1);
view.setCreator(creator);
view.add(creator);
view.add(gmsJoinLeaveMemberId);
InstallViewMessage msg = getInstallViewMessage(view, creator, false);
msg.setSender(creator);
gmsJoinLeave.processMessage(msg);
assertTrue("Expected it to stop being coordinator", !gmsJoinLeave.isCoordinator());
}
private InstallViewMessage getInstallViewMessage(GMSMembershipView view, Object credentials,
boolean preparing) {
InstallViewMessage installViewMessage = new InstallViewMessage(view, credentials, preparing);
installViewMessage.setSender(gmsJoinLeaveMemberId);
return installViewMessage;
}
@Test
public void testNetworkPartitionDetected() throws Exception {
initMocks(true);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
// set up a view with sufficient members, then create a new view
// where enough weight is lost to cause a network partition
List<MemberIdentifier> mbrs = new LinkedList<>();
mbrs.add(mockMembers[0]);
mbrs.add(mockMembers[1]);
mbrs.add(mockMembers[2]);
mbrs.add(gmsJoinLeaveMemberId);
mockMembers[1].setMemberWeight((byte) 20);
GMSMembershipView newView =
new GMSMembershipView(mockMembers[0], gmsJoinLeave.getView().getViewId() + 1, mbrs);
InstallViewMessage installViewMessage = getInstallViewMessage(newView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
Set<MemberIdentifier> crashes = new HashSet<>();
crashes.add(mockMembers[1]);
crashes.add(mockMembers[2]);
mbrs = new LinkedList<>(mbrs);
mbrs.remove(mockMembers[1]);
mbrs.remove(mockMembers[2]);
GMSMembershipView partitionView =
new GMSMembershipView(mockMembers[0], newView.getViewId() + 1, mbrs, Collections.emptySet(),
crashes);
installViewMessage = getInstallViewMessage(partitionView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
verify(manager).forceDisconnect(isA(String.class));
verify(manager).quorumLost(crashes, newView);
}
// Possibly modify test to check for network partition message in the force disconnect
@Test
public void testNetworkPartitionMessageReceived() throws Exception {
initMocks();
becomeCoordinatorForTest(gmsJoinLeave);
NetworkPartitionMessage message = new NetworkPartitionMessage();
gmsJoinLeave.processMessage(message);
verify(manager).forceDisconnect(isA(String.class));
}
@Test
public void testQuorumLossNotificationWithNetworkPartitionDetectionDisabled() throws Exception {
initMocks(false);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
// set up a view with sufficient members, then create a new view
// where enough weight is lost to cause a network partition
List<MemberIdentifier> mbrs = new LinkedList<>();
Set<MemberIdentifier> shutdowns = new HashSet<>();
Set<MemberIdentifier> crashes = new HashSet<>();
mbrs.add(mockMembers[0]);
mbrs.add(mockMembers[1]);
mbrs.add(mockMembers[2]);
mbrs.add(gmsJoinLeaveMemberId);
mockMembers[1].setMemberWeight((byte) 20);
GMSMembershipView newView =
new GMSMembershipView(mockMembers[0], gmsJoinLeave.getView().getViewId() + 1, mbrs,
shutdowns, crashes);
InstallViewMessage installViewMessage = getInstallViewMessage(newView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
crashes = new HashSet<>(crashes);
crashes.add(mockMembers[1]);
crashes.add(mockMembers[2]);
mbrs = new LinkedList<>(mbrs);
mbrs.remove(mockMembers[1]);
mbrs.remove(mockMembers[2]);
GMSMembershipView partitionView =
new GMSMembershipView(mockMembers[0], newView.getViewId() + 1, mbrs, shutdowns, crashes);
installViewMessage = getInstallViewMessage(partitionView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
verify(manager, never()).forceDisconnect(isA(String.class));
verify(manager).quorumLost(crashes, newView);
}
@Test
public void testConflictingPrepare() throws Exception {
initMocks(true);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView gmsView = gmsJoinLeave.getView();
GMSMembershipView newView = new GMSMembershipView(gmsView, gmsView.getViewId() + 6);
InstallViewMessage msg = getInstallViewMessage(newView, null, true);
gmsJoinLeave.processMessage(msg);
GMSMembershipView alternateView = new GMSMembershipView(gmsView, gmsView.getViewId() + 1);
msg = getInstallViewMessage(alternateView, null, true);
gmsJoinLeave.processMessage(msg);
assertTrue(gmsJoinLeave.getPreparedView().equals(newView));
}
@Test
public void testNoViewAckCausesRemovalMessage() throws Exception {
initMocks(true);
when(healthMonitor.checkIfAvailable(isA(MemberIdentifier.class), isA(String.class),
isA(Boolean.class))).thenReturn(false);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId));
GMSMembershipView oldView = gmsJoinLeave.getView();
GMSMembershipView newView = new GMSMembershipView(oldView, oldView.getViewId() + 1);
// the new view will remove the old coordinator (normal shutdown) and add a new member
// who will not ack the view. This should cause it to be removed from the system
// with a RemoveMemberMessage
newView.add(mockMembers[2]);
newView.remove(mockMembers[0]);
InstallViewMessage installViewMessage = getInstallViewMessage(newView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
// this test's member-timeout * 3
await()
.until(() -> gmsJoinLeave.getView().getViewId() != oldView.getViewId());
assertTrue(gmsJoinLeave.isCoordinator());
// wait for suspect processing
verify(healthMonitor, timeout(10000).atLeast(1)).checkIfAvailable(
isA(MemberIdentifier.class),
isA(String.class), isA(Boolean.class));
// verify(messenger, atLeast(1)).send(isA(RemoveMemberMessage.class));
}
/**
* This tests a member shutdown using the memberShutdown call (simulating the call from
* DistributionManager) The gmsJoinLeaveMemberId is not the coordinator but should now become the
* coordinator.
*/
@Test
public void testCoordinatorShutsdownAndWeBecomeCoordinatorAndSendOutCorrectView()
throws Exception {
initMocks(false);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], gmsJoinLeaveMemberId,
mockMembers[1], mockMembers[2], mockMembers[3]));
Assert.assertFalse(gmsJoinLeave.isCoordinator());
// The coordinator shuts down
gmsJoinLeave.memberShutdown(mockMembers[0], "Shutdown");
GMSMembershipView nextView = gmsJoinLeave.getViewCreator().initialView;
assertTrue(gmsJoinLeave.isCoordinator());
assertTrue(nextView.getCoordinator().equals(gmsJoinLeaveMemberId));
assertTrue(nextView.getMembers().contains(mockMembers[1]));
assertTrue(nextView.getMembers().contains(mockMembers[2]));
assertTrue(nextView.getMembers().contains(mockMembers[3]));
}
/**
* This tests a member shutdown using the memberShutdown call (simulating the call from
* DistributionManager) The gmsJoinLeaveMemberId is not the coordinator but should now become the
* coordinator and remove all members that have sent us leave requests prior to us becoming
* coordinator
*/
@Test
public void testCoordinatorAndOthersShutdownAndWeBecomeCoordinatorProcessQueuedUpLeaveMessages()
throws Exception {
initMocks(false);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], mockMembers[1],
mockMembers[2], gmsJoinLeaveMemberId, mockMembers[3]));
Assert.assertFalse(gmsJoinLeave.isCoordinator());
// The coordinator and other members shutdown
gmsJoinLeave.memberShutdown(mockMembers[1], "Shutdown");
gmsJoinLeave.memberShutdown(mockMembers[2], "Shutdown");
gmsJoinLeave.memberShutdown(mockMembers[0], "Shutdown");
GMSMembershipView nextView = gmsJoinLeave.getViewCreator().initialView;
assertTrue(gmsJoinLeave.isCoordinator());
assertTrue(nextView.getCoordinator().equals(gmsJoinLeaveMemberId));
Assert.assertFalse(nextView.getMembers().contains(mockMembers[1]));
Assert.assertFalse(nextView.getMembers().contains(mockMembers[2]));
assertTrue(nextView.getMembers().contains(mockMembers[3]));
}
/**
* If a locator is started and sends out a view to take control of the cluster another member that
* is also in the process of sending out a view should relinquish control to the new locator.
*
*/
@Test
public void testCoordinatorGetsConflictingViewFromLocator() throws Exception {
// create the GMSJoinLeave instance we'll be testing
initMocks(false);
MemberIdentifier otherMember = mockMembers[0];
gmsJoinLeaveMemberId.setVmKind(MemberIdentifier.NORMAL_DM_TYPE);
List<MemberIdentifier> members = createMemberList(gmsJoinLeaveMemberId, otherMember);
prepareAndInstallView(gmsJoinLeaveMemberId, members);
GMSMembershipView installedView = gmsJoinLeave.getView();
gmsJoinLeave.unitTesting.add("noRandomViewChange"); // keep view numbers predictable
// create a view coming from the locator that conflicts with the installed view
MemberIdentifier locatorMemberId = MemberIdentifierUtil.createMemberID(
mockMembers[mockMembers.length - 1].getMembershipPort() + 1);
locatorMemberId.setVmKind(MemberIdentifier.LOCATOR_DM_TYPE);
List<MemberIdentifier> newMemberList = new ArrayList<>(members);
newMemberList.add(locatorMemberId);
GMSMembershipView locatorView =
new GMSMembershipView(locatorMemberId, installedView.getViewId() + 10, newMemberList);
// start the process to make our GMSJoinLeave become coordinator. It will send out a view
// and want an ACK from
synchronized (gmsJoinLeave.getViewInstallationLock()) {
gmsJoinLeave.becomeCoordinator();
}
await()
.until(() -> gmsJoinLeave.prepareProcessor.isWaiting());
int newViewId = 6; // becomeCoordinator will bump the initial view Id by 5
ViewAckMessage msg = new ViewAckMessage(gmsJoinLeaveMemberId, newViewId, true);
msg.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(msg);
// ack the view on behalf of the other member, returning a conflicting view coming from a
// locator that is trying to become coordinator
msg = new ViewAckMessage(newViewId, gmsJoinLeaveMemberId, locatorView);
msg.setSender(otherMember);
gmsJoinLeave.processMessage(msg);
await()
.until(() -> gmsJoinLeave.getViewCreator() != null);
await()
.until(() -> gmsJoinLeave.getViewCreator().getAbandonedViewCount() > 0);
assertEquals(installedView, gmsJoinLeave.getView());
}
/**
* In a scenario where we have a member leave at the same time as an install view The member that
* left should be recorded on all members, if the coordinator also happens to leave, the new
* coordinator should be able to process the new view correctly
*/
@Test
public void testTimingWhereInstallViewComeAndDoesNotClearOutLeftMembersList() throws Exception {
initMocks(false);
prepareAndInstallView(mockMembers[0], createMemberList(mockMembers[0], mockMembers[1],
mockMembers[2], gmsJoinLeaveMemberId, mockMembers[3]));
Assert.assertFalse(gmsJoinLeave.isCoordinator());
// The coordinator and other members shutdown
gmsJoinLeave.memberShutdown(mockMembers[1], "Shutdown");
gmsJoinLeave.memberShutdown(mockMembers[2], "Shutdown");
// Install a view that still contains one of the left members (as if something like a new
// member, triggered a new view before coordinator leaves)
GMSMembershipView netView = new GMSMembershipView(mockMembers[0], 3/* new view id */,
createMemberList(mockMembers[0], gmsJoinLeaveMemberId, mockMembers[1], mockMembers[3]));
InstallViewMessage installViewMessage = getInstallViewMessage(netView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
// Now coordinator leaves
gmsJoinLeave.memberShutdown(mockMembers[0], "Shutdown");
GMSMembershipView nextView = gmsJoinLeave.getViewCreator().initialView;
assertTrue(gmsJoinLeave.isCoordinator());
assertTrue(nextView.getCoordinator().equals(gmsJoinLeaveMemberId));
Assert.assertFalse(nextView.getMembers().contains(mockMembers[1]));
Assert.assertFalse(nextView.getMembers().contains(mockMembers[2]));
assertTrue(nextView.getMembers().contains(mockMembers[3]));
}
@Test
public void testViewBroadcaster() throws Exception {
initMocks();
List<MemberIdentifier> members = new ArrayList<>(Arrays.asList(mockMembers));
gmsJoinLeaveMemberId.setVmViewId(1);
members.add(gmsJoinLeaveMemberId);
prepareAndInstallView(gmsJoinLeaveMemberId, members);
becomeCoordinatorForTest(gmsJoinLeave);
GMSJoinLeave.ViewBroadcaster b = gmsJoinLeave.new ViewBroadcaster();
b.run();
verify(messenger).sendUnreliably(isA(InstallViewMessage.class));
}
private void installView(int viewId, MemberIdentifier coordinator,
List<MemberIdentifier> members) throws IOException {
// prepare the view
GMSMembershipView netView = new GMSMembershipView(coordinator, viewId, members);
InstallViewMessage installViewMessage = getInstallViewMessage(netView, credentials, false);
gmsJoinLeave.processMessage(installViewMessage);
// verify(messenger).send(isA(ViewAckMessage.class));
}
@Test
public void testIgnoreoldView() throws Exception {
initMocks(false);
installView(3, mockMembers[0], createMemberList(mockMembers[0], mockMembers[1], mockMembers[2],
gmsJoinLeaveMemberId, mockMembers[3]));
// now try to intall old view..
installView(1, mockMembers[0], createMemberList(mockMembers[0], mockMembers[1], mockMembers[2],
gmsJoinLeaveMemberId, mockMembers[3]));
assertFalse("Expected view id is 3 but found " + gmsJoinLeave.getView().getViewId(),
gmsJoinLeave.getView().getViewId() == 1);
}
@Test
public void testClearViewRequests() throws Exception {
try {
initMocks(false);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
gmsJoinLeave.processMessage(
new JoinRequestMessage(mockMembers[0], mockMembers[0], credentials, -1, 0));
int viewRequests = gmsJoinLeave.getViewRequests().size();
assertEquals("There should be 1 viewRequest", 1, viewRequests);
await()
.until(() -> gmsJoinLeave.getViewRequests().size(), equalTo(0));
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
/***
* validating ViewReplyProcessor's memberSuspected, processLeaveRequest, processRemoveRequest,
* processViewResponse method
*/
@Test
public void testViewReplyProcessor() throws Exception {
try {
initMocks(false);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
Set<MemberIdentifier> recips = new HashSet<>();
recips.add(mockMembers[0]);
recips.add(mockMembers[1]);
recips.add(mockMembers[2]);
recips.add(mockMembers[3]);
ViewReplyProcessor prepareProcessor = gmsJoinLeave.getPrepareViewReplyProcessor();
prepareProcessor.initialize(1, recips);
assertTrue("Prepare processor should be waiting ",
gmsJoinLeave.testPrepareProcessorWaiting());
prepareProcessor.memberSuspected(mockMembers[0]);
prepareProcessor.processLeaveRequest(mockMembers[1]);
prepareProcessor.processRemoveRequest(mockMembers[2]);
prepareProcessor.processViewResponse(1, mockMembers[3], null);
assertFalse("Prepare processor should not be waiting ",
gmsJoinLeave.testPrepareProcessorWaiting());
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
/***
* validating ViewReplyProcessor's processPendingRequests method
*/
@Test
public void testViewReplyProcessor2() throws Exception {
try {
initMocks(false);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
Set<MemberIdentifier> recips = new HashSet<>();
recips.add(mockMembers[0]);
recips.add(mockMembers[1]);
recips.add(mockMembers[2]);
recips.add(mockMembers[3]);
ViewReplyProcessor prepareProcessor = gmsJoinLeave.getPrepareViewReplyProcessor();
prepareProcessor.initialize(1, recips);
assertTrue("Prepare processor should be waiting ",
gmsJoinLeave.testPrepareProcessorWaiting());
Set<MemberIdentifier> pendingLeaves = new HashSet<>();
pendingLeaves.add(mockMembers[0]);
Set<MemberIdentifier> pendingRemovals = new HashSet<>();
pendingRemovals.add(mockMembers[1]);
prepareProcessor.processPendingRequests(pendingLeaves, pendingRemovals);
prepareProcessor.processViewResponse(1, mockMembers[2], null);
prepareProcessor.processViewResponse(1, mockMembers[3], null);
assertFalse("Prepare processor should not be waiting ",
gmsJoinLeave.testPrepareProcessorWaiting());
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
// With the removal of the JoinResponse message from GMSJoinLeave.processJoinRequest (GEODE-870)
// This test now seems to be invalid
// @Test
// public void testJoinResponseMsgWithBecomeCoordinator() throws Exception {
// initMocks(false);
// gmsJoinLeaveMemberId.getNetMember().setPreferredForCoordinator(false);
// JoinRequestMessage reqMsg = new JoinRequestMessage(gmsJoinLeaveMemberId, mockMembers[0], null,
// 56734);
// GMSMember ids = new InternalDistributedMember("localhost", 97898);
// ids.getNetMember().setPreferredForCoordinator(true);
// gmsJoinLeave.processMessage(reqMsg);
// ArgumentCaptor<JoinResponseMessage> ac = ArgumentCaptor.forClass(JoinResponseMessage.class);
// verify(messenger).send(ac.capture());
//
// assertTrue("Should have asked for becoming a coordinator",
// ac.getValue().getBecomeCoordinator());
// }
@Test
public void testNetworkPartionMessage() throws Exception {
try {
initMocks(true);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
installView(1, gmsJoinLeaveMemberId, createMemberList(mockMembers[0],
gmsJoinLeaveMemberId));
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeaveMemberId, mockMembers[0], "crashed");
msg.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(msg);
Timeout to =
new Timeout(3 * MembershipConfig.MEMBER_REQUEST_COLLECTION_INTERVAL, new Times(1));
verify(messenger, to).send(isA(NetworkPartitionMessage.class));
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
@Test
public void testViewIgnoredAfterShutdown() throws Exception {
try {
initMocks(true);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
installView(1, gmsJoinLeaveMemberId, createMemberList(mockMembers[0], mockMembers[1],
mockMembers[2], gmsJoinLeaveMemberId, mockMembers[3]));
gmsJoinLeave.stop();
for (int i = 1; i < 4; i++) {
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeaveMemberId, mockMembers[i], "crashed");
msg.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(msg);
}
Timeout to = new Timeout(2 * MembershipConfig.MEMBER_REQUEST_COLLECTION_INTERVAL, never());
verify(messenger, to).send(isA(NetworkPartitionMessage.class));
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
@Test
public void testViewNotSentWhenShuttingDown() throws Exception {
try {
initMocks(false);
System.setProperty(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY, "true");
gmsJoinLeave.join();
installView(1, gmsJoinLeaveMemberId, createMemberList(mockMembers[0], mockMembers[1],
mockMembers[2], gmsJoinLeaveMemberId, mockMembers[3]));
assertTrue(gmsJoinLeave.getViewCreator().isAlive());
assertTrue(testLocator.isCoordinator);
when(manager.shutdownInProgress()).thenReturn(Boolean.TRUE);
for (int i = 1; i < 4; i++) {
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeaveMemberId, mockMembers[i], "crashed");
msg.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(msg);
}
await("waiting for view creator to stop")
.until(() -> !gmsJoinLeave.getViewCreator().isAlive());
assertEquals(1, gmsJoinLeave.getView().getViewId());
assertFalse(testLocator.isCoordinator);
} finally {
System.getProperties().remove(GMSJoinLeave.BYPASS_DISCOVERY_PROPERTY);
}
}
@Test
public void testPreparedViewFoundDuringBecomeCoordinator() throws Exception {
initMocks(false);
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
// a new member is joining
GMSMembershipView preparedView =
new GMSMembershipView(gmsJoinLeave.getView(), gmsJoinLeave.getView().getViewId() + 5);
mockMembers[1].setVmViewId(preparedView.getViewId());
preparedView.add(mockMembers[1]);
InstallViewMessage msg = getInstallViewMessage(preparedView, null, true);
gmsJoinLeave.processMessage(msg);
becomeCoordinatorForTest(gmsJoinLeave);
Thread.sleep(2000);
ViewCreator vc = gmsJoinLeave.getViewCreator();
int viewId = 0;
if (gmsJoinLeave.getPreparedView() == null) {
viewId = gmsJoinLeave.getView().getViewId();
} else {
viewId = gmsJoinLeave.getPreparedView().getViewId();
}
ViewAckMessage vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(vack);
vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(mockMembers[1]);
gmsJoinLeave.processMessage(vack);
vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(vack);
await("view creator finishes").until(() -> vc.waiting);
GMSMembershipView newView = gmsJoinLeave.getView();
System.out.println("new view is " + newView);
assertTrue(newView.contains(mockMembers[1]));
assertTrue(newView.getViewId() > preparedView.getViewId());
}
@Test
public void testPublicKeyForNewMemberFromPreparedViewIsInstalledInNewView() throws Exception {
initMocks(false);
MemberIdentifier newMember = mockMembers[1];
prepareAndInstallView(gmsJoinLeaveMemberId,
createMemberList(gmsJoinLeaveMemberId, mockMembers[0]));
// a new member is joining
GMSMembershipView<MemberIdentifier> preparedView =
new GMSMembershipView(gmsJoinLeave.getView(), gmsJoinLeave.getView().getViewId() + 5);
for (MemberIdentifier member : preparedView.getMembers()) {
preparedView.setPublicKey(member, member.toString());
}
newMember.setVmViewId(preparedView.getViewId());
preparedView.add(newMember);
preparedView.setPublicKey(newMember, newMember.toString());
InstallViewMessage msg = getInstallViewMessage(preparedView, null, true);
gmsJoinLeave.processMessage(msg);
becomeCoordinatorForTest(gmsJoinLeave);
Thread.sleep(2000);
ViewCreator vc = gmsJoinLeave.getViewCreator();
int viewId = 0;
if (gmsJoinLeave.getPreparedView() == null) {
viewId = gmsJoinLeave.getView().getViewId();
} else {
viewId = gmsJoinLeave.getPreparedView().getViewId();
}
ViewAckMessage vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(mockMembers[0]);
gmsJoinLeave.processMessage(vack);
vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(newMember);
gmsJoinLeave.processMessage(vack);
vack = new ViewAckMessage(gmsJoinLeaveMemberId, viewId, true);
vack.setSender(gmsJoinLeaveMemberId);
gmsJoinLeave.processMessage(vack);
await("view creator finishes").until(() -> vc.waiting);
GMSMembershipView newView = gmsJoinLeave.getView();
System.out.println("new view is " + newView);
assertTrue(newView.contains(newMember));
assertNotNull(newView.getPublicKey(newMember));
}
private GMSMembershipView createView() {
List<MemberIdentifier> mbrs = new LinkedList<>();
Set<MemberIdentifier> shutdowns = new HashSet<>();
Set<MemberIdentifier> crashes = new HashSet<>();
mbrs.add(mockMembers[0]);
mbrs.add(mockMembers[1]);
mbrs.add(mockMembers[2]);
mbrs.add(gmsJoinLeaveMemberId);
// prepare the view
GMSMembershipView netView = new GMSMembershipView(mockMembers[0], 1, mbrs, shutdowns, crashes);
return netView;
}
@Test
public void testCoordinatorFindRequestSuccess() throws Exception {
initMocks(false);
mockRequestToServer(isA(HostAndPort.class));
boolean foundCoordinator = gmsJoinLeave.findCoordinator();
assertTrue(gmsJoinLeave.searchState.toString(), foundCoordinator);
assertEquals(gmsJoinLeave.searchState.possibleCoordinator, mockMembers[0]);
}
@Test
public void testCoordinatorFindRequestFailure() throws Exception {
try {
initMocks(false);
mockRequestToServer(eq(new HostAndPort("localhost", 12346)));
GMSMembershipView view = createView();
JoinResponseMessage jrm = new JoinResponseMessage(mockMembers[0], view, 0);
gmsJoinLeave.setJoinResponseMessage(jrm);
assertThatThrownBy(gmsJoinLeave::join)
.isInstanceOf(MembershipConfigurationException.class);
} finally {
}
}
@Test
public void testJoinFailureWhenSleepInterrupted() throws Exception {
initMocks(false);
mockRequestToServer(isA(HostAndPort.class));
when(mockConfig.getMemberTimeout()).thenReturn(100L);
when(mockConfig.getJoinTimeout()).thenReturn(1000L);
GMSJoinLeave spyGmsJoinLeave = spy(gmsJoinLeave);
when(spyGmsJoinLeave.pauseIfThereIsNoCoordinator(-1, GMSJoinLeave.JOIN_RETRY_SLEEP))
.thenThrow(new InterruptedException());
assertThatThrownBy(spyGmsJoinLeave::join)
.isInstanceOf(MembershipConfigurationException.class)
.hasMessageContaining("Retry sleep interrupted");
}
@Test
public void testJoinFailureWhenTimeout() throws Exception {
initMocks(false);
mockRequestToServer(isA(HostAndPort.class));
assertThatThrownBy(() -> gmsJoinLeave.join())
.isInstanceOf(MembershipConfigurationException.class)
.hasMessageContaining("Operation timed out");
}
@Test
public void testPauseIfThereIsNoCoordinator() throws InterruptedException {
locatorClient = mock(TcpClient.class);
gmsJoinLeave = new GMSJoinLeave(locatorClient);
assertThat(gmsJoinLeave.pauseIfThereIsNoCoordinator(-1, GMSJoinLeave.JOIN_RETRY_SLEEP))
.isFalse();
assertThat(gmsJoinLeave.pauseIfThereIsNoCoordinator(1, GMSJoinLeave.JOIN_RETRY_SLEEP)).isTrue();
}
@Test
public void testJoinFailureWhenNoLocator() throws Exception {
final String locator1 = "locator1[12345]";
final String locator2 = "locator2[54321]";
locatorClient = mock(TcpClient.class);
initMocks(false, false, locator1 + ',' + locator2, locator1);
when(locatorClient.requestToServer(any(), any(), anyInt(), anyBoolean()))
.thenThrow(IOException.class);
assertThatThrownBy(gmsJoinLeave::join)
.isInstanceOf(MembershipConfigurationException.class)
.hasMessageContaining(
"Could not contact any of the locators: [HostAndPort[locator1:12345], HostAndPort[locator2:54321]]")
.hasCauseInstanceOf(IOException.class);
}
private void mockRequestToServer(HostAndPort hostAndPort)
throws IOException, ClassNotFoundException {
HashSet<MemberIdentifier> registrants = new HashSet<>();
registrants.add(mockMembers[0]);
FindCoordinatorResponse fcr = new FindCoordinatorResponse(mockMembers[0], mockMembers[0], false,
null, registrants, false, true, null);
when(locatorClient.requestToServer(hostAndPort,
isA(FindCoordinatorRequest.class), anyInt(), anyBoolean()))
.thenReturn(fcr);
}
private void waitForViewAndFinalCheckInProgress(int viewId) throws InterruptedException {
// wait for the view processing thread to collect and process the requests
int sleeps = 0;
while (!gmsJoinLeave.isStopping() && (gmsJoinLeave.getView().getViewId() == viewId)) {
if (sleeps++ > 20) {
System.out.println("view requests: " + gmsJoinLeave.getViewRequests());
System.out.println("current view: " + gmsJoinLeave.getView());
throw new RuntimeException("timeout waiting for view #" + viewId);
}
Thread.sleep(1000);
System.out.println("Empty sleeps " + sleeps + " stopping: " + gmsJoinLeave.isStopping());
}
}
class GMSJoinLeaveTest extends GMSJoinLeave {
public GMSJoinLeaveTest(final TcpClient locatorClient) {
super(locatorClient);
}
@Override
boolean checkIfAvailable(MemberIdentifier fmbr) {
if (removeMember != null) {
try {
if (removeMember.equals(fmbr)) {
GMSJoinLeaveJUnitTest.this.processRemoveMessage(fmbr);
Thread.sleep(1000000);
}
} catch (InterruptedException ignore) {
}
return true;
} else if (leaveMember != null) {
try {
if (leaveMember.equals(fmbr)) {
GMSJoinLeaveJUnitTest.this.processLeaveMessage(fmbr);
Thread.sleep(1000000);
}
} catch (InterruptedException ignore) {
}
return true;
} else {
return super.checkIfAvailable(fmbr);
}
}
}
@Test
public void testRemoveRequestWhileWaitingForFinalResponse() throws Exception {
initMocks(true, true);
becomeCoordinatorForTest(gmsJoinLeave);
installView();
int viewId = gmsJoinLeave.getView().getViewId();
System.out.println("Current viewid " + viewId);
this.removeMember = mockMembers[0];
processJoinMessage(gmsJoinLeave.getMemberID(), mockMembers[2], 98989);
waitForViewAndFinalCheckInProgress(viewId);
this.removeMember = null;
assertTrue("testFlagForRemovalRequest should be true",
gmsJoinLeave.getViewCreator().getTestFlagForRemovalRequest());
}
@Test
public void testLeaveRequestWhileWaitingForFinalResponse() throws Exception {
initMocks(true, true);
becomeCoordinatorForTest(gmsJoinLeave);
installView();
int viewId = gmsJoinLeave.getView().getViewId();
System.out.println("Current viewid " + viewId);
this.leaveMember = mockMembers[0];
processJoinMessage(gmsJoinLeave.getMemberID(), mockMembers[2], 98989);
waitForViewAndFinalCheckInProgress(viewId);
this.leaveMember = null;
assertTrue("testFlagForRemovalRequest should be true",
gmsJoinLeave.getViewCreator().getTestFlagForRemovalRequest());
}
@Test
public void testMulticastDiscoveryNotAllowed() {
Services services = mock(Services.class);
MembershipConfig membershipConfig = mock(MembershipConfig.class);
when(membershipConfig.getLocators()).thenReturn("");
when(membershipConfig.getMcastPort()).thenReturn(1234);
when(membershipConfig.getMcastAddress()).thenReturn("scooby.dooby.doo");
when(services.getConfig()).thenReturn(membershipConfig);
GMSJoinLeave joinLeave = new GMSJoinLeave(null);
try {
joinLeave.init(services);
throw new Error(
"expected a GemFireConfigException to be thrown because no locators are configured");
} catch (MembershipConfigurationException e) {
// expected
}
}
// GEODE-8240 could cause this member's identifier to have the wrong version so patch it up
@Test
public void repairWrongVersionInView() throws Exception {
initMocks();
List<MemberIdentifier> viewmembers =
Arrays.asList(new MemberIdentifier[] {mockMembers[0], gmsJoinLeaveMemberId});
final GMSMembershipView<MemberIdentifier> viewWithWrongVersion =
new GMSMembershipView<>(mockMembers[0], 2, viewmembers);
// clone member ID
final MemberIdentifierImpl myMemberIDWithWrongVersion =
new MemberIdentifierImpl(gmsJoinLeaveMemberId.getMemberData());
// this test must live in the 1.12 and later lines so pick a pre-1.12 version
final KnownVersion oldVersion = KnownVersion.GEODE_1_11_0;
myMemberIDWithWrongVersion.setVersionForTest(oldVersion);
viewWithWrongVersion.remove(gmsJoinLeaveMemberId);
viewWithWrongVersion.add(myMemberIDWithWrongVersion);
gmsJoinLeave.installView(viewWithWrongVersion);
assertThat(
gmsJoinLeave.getView().getCanonicalID(gmsJoinLeaveMemberId).getVersion())
.isEqualTo(KnownVersion.getCurrentVersion());
}
private void becomeCoordinatorForTest(GMSJoinLeave gmsJoinLeave) {
synchronized (gmsJoinLeave.getViewInstallationLock()) {
gmsJoinLeave.becomeCoordinator();
}
}
private void installView() throws Exception {
final int viewInstallationTime = 15000;
GMSMembershipView oldView = null;
long giveup = System.currentTimeMillis() + viewInstallationTime;
while (System.currentTimeMillis() < giveup && oldView == null) {
Thread.sleep(500);
oldView = gmsJoinLeave.getView();
}
assertTrue(oldView != null); // it should have become coordinator and installed a view
GMSMembershipView newView = new GMSMembershipView(oldView, oldView.getViewId() + 1);
newView.add(mockMembers[0]);
newView.add(mockMembers[1]);
gmsJoinLeave.installView(newView);
}
private void processJoinMessage(MemberIdentifier coordinator,
MemberIdentifier newMember, int port) {
JoinRequestMessage reqMsg = new JoinRequestMessage(coordinator, newMember, null, port, 0);
gmsJoinLeave.processMessage(reqMsg);
}
private void processRemoveMessage(MemberIdentifier rMember) {
RemoveMemberMessage msg =
new RemoveMemberMessage(gmsJoinLeave.getMemberID(), rMember, "testing");
msg.setSender(gmsJoinLeave.getMemberID());
gmsJoinLeave.processMessage(msg);
}
private void processLeaveMessage(MemberIdentifier rMember) {
LeaveRequestMessage msg =
new LeaveRequestMessage(gmsJoinLeave.getMemberID(), rMember, "testing");
msg.setSender(rMember);
gmsJoinLeave.processMessage(msg);
}
}