/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.cluster;
import com.hazelcast.config.Config;
import com.hazelcast.config.ListenerConfig;
import com.hazelcast.core.Cluster;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.InitialMembershipEvent;
import com.hazelcast.core.InitialMembershipListener;
import com.hazelcast.core.Member;
import com.hazelcast.core.MemberAttributeEvent;
import com.hazelcast.core.MembershipAdapter;
import com.hazelcast.core.MembershipEvent;
import com.hazelcast.core.MembershipListener;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastParallelClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.ParallelTest;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@RunWith(HazelcastParallelClassRunner.class)
@Category({QuickTest.class, ParallelTest.class})
public class ClusterMembershipListenerTest extends HazelcastTestSupport {
@Test(expected = NullPointerException.class)
public void testAddMembershipListener_whenNullListener() {
HazelcastInstance hz = createHazelcastInstance();
Cluster cluster = hz.getCluster();
cluster.addMembershipListener(null);
}
@Test
public void testAddMembershipListener_whenListenerRegisteredTwice() {
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance hz1 = factory.newHazelcastInstance();
Cluster cluster = hz1.getCluster();
final MembershipListener membershipListener = mock(MembershipListener.class);
String id1 = cluster.addMembershipListener(membershipListener);
String id2 = cluster.addMembershipListener(membershipListener);
// first we check if the registration id's are different
assertNotEquals(id1, id2);
// an now we make sure that if a member joins the cluster, the same interface gets invoked twice.
HazelcastInstance hz2 = factory.newHazelcastInstance();
assertTrueEventually(new AssertTask() {
@Override
public void run() throws Exception {
//now we verify that the memberAdded method is called twice.
verify(membershipListener, times(2)).memberAdded(any(MembershipEvent.class));
}
});
}
@Test(expected = NullPointerException.class)
public void testRemoveMembershipListener_whenNullListener() {
HazelcastInstance hz = createHazelcastInstance();
Cluster cluster = hz.getCluster();
cluster.removeMembershipListener(null);
}
@Test
public void testRemoveMembershipListener_whenNonExistingRegistrationId() {
HazelcastInstance hz = createHazelcastInstance();
Cluster cluster = hz.getCluster();
boolean result = cluster.removeMembershipListener("notexist");
assertFalse(result);
}
@Test
public void testRemoveMembershipListener() {
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance hz1 = factory.newHazelcastInstance();
Cluster cluster = hz1.getCluster();
MembershipListener membershipListener = mock(MembershipListener.class);
String id = cluster.addMembershipListener(membershipListener);
boolean removed = cluster.removeMembershipListener(id);
assertTrue(removed);
// now we add a member
HazelcastInstance hz2 = factory.newHazelcastInstance();
// and verify that the listener isn't called.
verify(membershipListener, never()).memberAdded(any(MembershipEvent.class));
}
@Test
public void testMembershipListener() {
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance hz1 = factory.newHazelcastInstance();
MembershipListenerImpl listener = new MembershipListenerImpl();
hz1.getCluster().addMembershipListener(listener);
//start a second instance
HazelcastInstance hz2 = factory.newHazelcastInstance();
assertEventuallySizeAtLeast(listener.events, 1);
assertMembershipAddedEvent(listener.events.get(0), hz2.getCluster().getLocalMember(),
hz1.getCluster().getLocalMember(), hz2.getCluster().getLocalMember());
//terminate the second instance
Member member2 = hz2.getCluster().getLocalMember();
hz2.shutdown();
assertEventuallySizeAtLeast(listener.events, 2);
assertMembershipRemovedEvent(listener.events.get(1), member2, hz1.getCluster().getLocalMember());
}
@Test
public void testMembershipListenerSequentialInvocation() throws Exception {
final int nodeCount = 10;
final TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(nodeCount);
final CountDownLatch eventLatch = new CountDownLatch(nodeCount - 1);
final CountDownLatch nodeLatch = new CountDownLatch(nodeCount - 1);
Config config = new Config()
.addListenerConfig(new ListenerConfig().setImplementation(newAddMemberListener(eventLatch)));
// first node has listener
factory.newHazelcastInstance(config);
for (int i = 1; i < nodeCount; i++) {
spawn(new Runnable() {
public void run() {
factory.newHazelcastInstance(new Config());
nodeLatch.countDown();
}
});
}
assertOpenEventually(nodeLatch);
assertOpenEventually(eventLatch);
}
private static MembershipAdapter newAddMemberListener(final CountDownLatch eventLatch) {
return new MembershipAdapter() {
// flag to check listener is not called concurrently
final AtomicBoolean flag = new AtomicBoolean(false);
public void memberAdded(MembershipEvent membershipEvent) {
if (flag.compareAndSet(false, true)) {
sleepMillis((int) (Math.random() * 500) + 50);
eventLatch.countDown();
flag.set(false);
}
}
};
}
@Test
public void testInitialMembershipListener() {
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance hz1 = factory.newHazelcastInstance();
InitialMembershipListenerImpl listener = new InitialMembershipListenerImpl();
hz1.getCluster().addMembershipListener(listener);
assertEventuallySizeAtLeast(listener.events, 1);
assertInitialMembershipEvent(listener.events.get(0), hz1.getCluster().getLocalMember());
HazelcastInstance hz2 = factory.newHazelcastInstance();
assertEventuallySizeAtLeast(listener.events, 2);
assertMembershipAddedEvent(listener.events.get(1), hz2.getCluster().getLocalMember(),
hz1.getCluster().getLocalMember(), hz2.getCluster().getLocalMember());
Member member2 = hz2.getCluster().getLocalMember();
hz2.shutdown();
assertEventuallySizeAtLeast(listener.events, 3);
assertMembershipRemovedEvent(listener.events.get(2), member2, hz1.getCluster().getLocalMember());
}
@Test
public void testInitialMembershipListenerRegistrationWithMultipleInitialMembers() {
TestHazelcastInstanceFactory factory = createHazelcastInstanceFactory(2);
HazelcastInstance hz1 = factory.newHazelcastInstance();
HazelcastInstance hz2 = factory.newHazelcastInstance();
InitialMembershipListenerImpl listener = new InitialMembershipListenerImpl();
hz1.getCluster().addMembershipListener(listener);
assertEventuallySizeAtLeast(listener.events, 1);
assertInitialMembershipEvent(listener.events.get(0), hz1.getCluster().getLocalMember(),
hz2.getCluster().getLocalMember());
}
public void assertInitialMembershipEvent(EventObject e, Member... expectedMembers) {
assertTrue(e instanceof InitialMembershipEvent);
InitialMembershipEvent initialMembershipEvent = (InitialMembershipEvent) e;
Set<Member> foundMembers = initialMembershipEvent.getMembers();
assertEquals(new HashSet<Member>(Arrays.asList(expectedMembers)), foundMembers);
}
public void assertMembershipAddedEvent(EventObject e, Member addedMember, Member... expectedMembers) {
assertMembershipEvent(e, MembershipEvent.MEMBER_ADDED, addedMember, expectedMembers);
}
public void assertMembershipRemovedEvent(EventObject e, Member addedMember, Member... expectedMembers) {
assertMembershipEvent(e, MembershipEvent.MEMBER_REMOVED, addedMember, expectedMembers);
}
public void assertMembershipEvent(EventObject e, int type, Member changedMember, Member... expectedMembers) {
assertTrue(e instanceof MembershipEvent);
MembershipEvent membershipEvent = (MembershipEvent) e;
Set<Member> foundMembers = membershipEvent.getMembers();
assertEquals(type, membershipEvent.getEventType());
assertEquals(changedMember, membershipEvent.getMember());
assertEquals(new HashSet<Member>(Arrays.asList(expectedMembers)), foundMembers);
}
public void assertEventuallySizeAtLeast(List list, int expectedSize) {
long startTimeMs = System.currentTimeMillis();
for (; ; ) {
if (list.size() >= expectedSize) {
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (System.currentTimeMillis() - startTimeMs > TimeUnit.SECONDS.toMillis(10)) {
fail("Timeout, size of the list didn't reach size: " + expectedSize + " in time");
}
}
}
private static class MembershipListenerImpl implements MembershipListener {
private List<EventObject> events = Collections.synchronizedList(new LinkedList<EventObject>());
public void memberAdded(MembershipEvent e) {
events.add(e);
}
public void memberRemoved(MembershipEvent e) {
events.add(e);
}
public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
}
}
private static class InitialMembershipListenerImpl implements InitialMembershipListener {
private List<EventObject> events = Collections.synchronizedList(new LinkedList<EventObject>());
public void init(InitialMembershipEvent e) {
events.add(e);
}
public void memberAdded(MembershipEvent e) {
events.add(e);
}
public void memberRemoved(MembershipEvent e) {
events.add(e);
}
public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
}
public void assertEventCount(int expected) {
assertEquals(expected, events.size());
}
}
}