Leader Election Pattern (#923)

* Fix issue #761: ThreadSafeDoubleCheckLocking.java: Instantiating by Reflection call will be successful if you do that firstly

* Create leader election module

* Create Interface of Instance and MessageManager

* Create implementations with token ring algorithm

* Change package structure.
Create basic message system.

* Implement heartbeat and heartbeat invoking message system

* Implement election message handler

* Add leader message handler

* Add main entry point

* Add comments

* Update README.md

* Fix checkstyle issue

* Add Unit Tests

* Add Unit Tests

* Add bully leader selection

* Change System.out to log print.
Add MIT license in each file.

* Add More java doc comments

* Add unit test

* Add unit tests
This commit is contained in:
Azureyjt
2019-10-08 23:29:59 +08:00
committed by Ilkka Seppälä
parent 41b8d80479
commit 90ea4506ca
22 changed files with 1719 additions and 0 deletions

View File

@ -0,0 +1,148 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Abstract class of all the instance implementation classes.
*/
public abstract class AbstractInstance implements Instance, Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class);
protected static final int HEARTBEAT_INTERVAL = 5000;
protected MessageManager messageManager;
protected Queue<Message> messageQueue;
protected final int localId;
protected int leaderId;
protected boolean alive;
/**
* Constructor of BullyInstance.
*/
public AbstractInstance(MessageManager messageManager, int localId, int leaderId) {
this.messageManager = messageManager;
this.messageQueue = new ConcurrentLinkedQueue<>();
this.localId = localId;
this.leaderId = leaderId;
this.alive = true;
}
/**
* The instance will execute the message in its message queue periodically once it is alive.
*/
@Override
public void run() {
while (true) {
if (!this.messageQueue.isEmpty()) {
this.processMessage(this.messageQueue.remove());
}
}
}
/**
* Once messages are sent to the certain instance, it will firstly be added to the queue and wait to be executed.
* @param message Message sent by other instances
*/
@Override
public void onMessage(Message message) {
messageQueue.offer(message);
}
/**
* Check if the instance is alive or not.
* @return {@code true} if the instance is alive.
*/
@Override
public boolean isAlive() {
return alive;
}
/**
* Set the health status of the certain instance.
* @param alive {@code true} for alive.
*/
@Override
public void setAlive(boolean alive) {
this.alive = alive;
}
/**
* Process the message according to its type.
* @param message Message polled from queue.
*/
private void processMessage(Message message) {
switch (message.getType()) {
case ELECTION:
LOGGER.info("Instance " + localId + " - Election Message handling...");
handleElectionMessage(message);
break;
case LEADER:
LOGGER.info("Instance " + localId + " - Leader Message handling...");
handleLeaderMessage(message);
break;
case HEARTBEAT:
LOGGER.info("Instance " + localId + " - Heartbeat Message handling...");
handleHeartbeatMessage(message);
break;
case ELECTION_INVOKE:
LOGGER.info("Instance " + localId + " - Election Invoke Message handling...");
handleElectionInvokeMessage();
break;
case LEADER_INVOKE:
LOGGER.info("Instance " + localId + " - Leader Invoke Message handling...");
handleLeaderInvokeMessage();
break;
case HEARTBEAT_INVOKE:
LOGGER.info("Instance " + localId + " - Heartbeat Invoke Message handling...");
handleHeartbeatInvokeMessage();
break;
default:
break;
}
}
/**
* Abstract methods to handle different types of message. These methods need to be implemented in concrete instance
* class to implement corresponding leader-selection pattern.
*/
protected abstract void handleElectionMessage(Message message);
protected abstract void handleElectionInvokeMessage();
protected abstract void handleLeaderMessage(Message message);
protected abstract void handleLeaderInvokeMessage();
protected abstract void handleHeartbeatMessage(Message message);
protected abstract void handleHeartbeatInvokeMessage();
}

View File

@ -0,0 +1,73 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Abstract class of all the message manager classes.
*/
public abstract class AbstractMessageManager implements MessageManager {
/**
* Contain all the instances in the system. Key is its ID, and value is the instance itself.
*/
protected Map<Integer, Instance> instanceMap;
/**
* Construtor of AbstractMessageManager
*/
public AbstractMessageManager(Map<Integer, Instance> instanceMap) {
this.instanceMap = instanceMap;
}
/**
* Find the next instance with smallest ID.
* @return The next instance.
*/
protected Instance findNextInstance(int currentId) {
Instance result = null;
List<Integer> candidateList = instanceMap.keySet()
.stream()
.filter((i) -> i > currentId && instanceMap.get(i).isAlive())
.sorted()
.collect(Collectors.toList());
if (candidateList.isEmpty()) {
int index = instanceMap.keySet()
.stream()
.filter((i) -> instanceMap.get(i).isAlive())
.sorted()
.collect(Collectors.toList())
.get(0);
result = instanceMap.get(index);
} else {
int index = candidateList.get(0);
result = instanceMap.get(index);
}
return result;
}
}

View File

@ -0,0 +1,49 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
/**
* Instance interface
*/
public interface Instance {
/**
* Check if the instance is alive or not.
* @return {@code true} if the instance is alive.
*/
boolean isAlive();
/**
* Set the health status of the certain instance.
* @param alive {@code true} for alive.
*/
void setAlive(boolean alive);
/**
* Consume messages from other instances.
* @param message Message sent by other instances
*/
void onMessage(Message message);
}

View File

@ -0,0 +1,76 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
import java.util.Objects;
/**
* Message used to transport data between instances.
*/
public class Message {
private MessageType type;
private String content;
public Message() {}
public Message(MessageType type, String content) {
this.type = type;
this.content = content;
}
public MessageType getType() {
return type;
}
public void setType(MessageType type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Message message = (Message) o;
return type == message.type && Objects.equals(content, message.content);
}
@Override
public int hashCode() {
return Objects.hash(type, content);
}
}

View File

@ -0,0 +1,60 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
/**
* MessageManager interface
*/
public interface MessageManager {
/**
* Send heartbeat message to leader instance to check whether the leader instance is alive.
* @param leaderId Instance ID of leader instance.
* @return {@code true} if leader instance is alive, or {@code false} if not.
*/
boolean sendHeartbeatMessage(int leaderId);
/**
* Send election message to other instances.
* @param currentId Instance ID of which sends this message.
* @param content Election message content.
* @return {@code true} if the message is accepted by the target instances.
*/
boolean sendElectionMessage(int currentId, String content);
/**
* Send new leader notification message to other instances.
* @param currentId Instance ID of which sends this message.
* @param leaderId Leader message content.
* @return {@code true} if the message is accepted by the target instances.
*/
boolean sendLeaderMessage(int currentId, int leaderId);
/**
* Send heartbeat invoke message. This will invoke heartbeat task in the target instance.
* @param currentId Instance ID of which sends this message.
*/
void sendHeartbeatInvokeMessage(int currentId);
}

View File

@ -0,0 +1,62 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
/**
* Message Type enum
*/
public enum MessageType {
/**
* Start the election. The content of the message stores ID(s) of the candidate instance(s).
*/
ELECTION,
/**
* Nodify the new leader. The content of the message should be the leader ID.
*/
LEADER,
/**
* Check health of current leader instance.
*/
HEARTBEAT,
/**
* Inform target instance to start election.
*/
ELECTION_INVOKE,
/**
* Inform target instance to notify all the other instance that it is the new leader.
*/
LEADER_INVOKE,
/**
* Inform target instance to start heartbeat.
*/
HEARTBEAT_INVOKE
}

View File

@ -0,0 +1,77 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import com.iluwatar.leaderelection.Instance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageManager;
import com.iluwatar.leaderelection.MessageType;
import java.util.HashMap;
import java.util.Map;
/**
* Example of how to use bully leader election. Initially 5 instances is created in the clould
* system, and the instance with ID 1 is set as leader. After the system is started stop the
* leader instance, and the new leader will be elected.
*/
public class BullyApp {
/**
* Program entry point
*/
public static void main(String[] args) {
Map<Integer, Instance> instanceMap = new HashMap<>();
MessageManager messageManager = new BullyMessageManager(instanceMap);
BullyInstance instance1 = new BullyInstance(messageManager, 1, 1);
BullyInstance instance2 = new BullyInstance(messageManager, 2, 1);
BullyInstance instance3 = new BullyInstance(messageManager, 3, 1);
BullyInstance instance4 = new BullyInstance(messageManager, 4, 1);
BullyInstance instance5 = new BullyInstance(messageManager, 5, 1);
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
instanceMap.put(4, instance4);
instanceMap.put(5, instance5);
instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, ""));
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
Thread thread3 = new Thread(instance3);
Thread thread4 = new Thread(instance4);
Thread thread5 = new Thread(instance5);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
instance1.setAlive(false);
}
}

View File

@ -0,0 +1,121 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import com.iluwatar.leaderelection.AbstractInstance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Impelemetation with bully algorithm. Each instance should have a sequential id and is able to
* communicate with other instances in the system. Initially the instance with smallest (or largest)
* ID is selected to be the leader. All the other instances send heartbeat message to leader periodically
* to check its health. If one certain instance finds the server done, it will send an election message
* to all the instances of which the ID is larger. If the target instance is alive, it will return an
* alive message (in this sample return true) and then send election message with its ID. If not,
* the original instance will send leader message to all the other instances.
*/
public class BullyInstance extends AbstractInstance {
private static final Logger LOGGER = LoggerFactory.getLogger(BullyInstance.class);
/**
* Constructor of BullyInstance.
*/
public BullyInstance(MessageManager messageManager, int localId, int leaderId) {
super(messageManager, localId, leaderId);
}
/**
* Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat
* to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not,
* it will start the election process.
*/
@Override
protected void handleHeartbeatInvokeMessage() {
try {
boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId);
if (isLeaderAlive) {
LOGGER.info("Instance " + localId + "- Leader is alive.");
Thread.sleep(HEARTBEAT_INTERVAL);
messageManager.sendHeartbeatInvokeMessage(localId);
} else {
LOGGER.info("Instance " + localId + "- Leader is not alive. Start election.");
boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId));
if (electionResult) {
LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification.");
messageManager.sendLeaderMessage(localId, localId);
}
}
} catch (InterruptedException e) {
LOGGER.info("Instance " + localId + "- Interrupted.");
}
}
/**
* Process election invoke message. Send election message to all the instances with smaller ID. If any
* one of them is alive, do nothing. If no instance alive, send leader message to all the alive instance
* and restart heartbeat.
*/
@Override
protected void handleElectionInvokeMessage() {
if (!isLeader()) {
LOGGER.info("Instance " + localId + "- Start election.");
boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId));
if (electionResult) {
LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification.");
leaderId = localId;
messageManager.sendLeaderMessage(localId, localId);
messageManager.sendHeartbeatInvokeMessage(localId);
}
}
}
/**
* Process leader message. Update local leader information.
*/
@Override
protected void handleLeaderMessage(Message message) {
leaderId = Integer.valueOf(message.getContent());
LOGGER.info("Instance " + localId + " - Leader update done.");
}
private boolean isLeader() {
return localId == leaderId;
}
/**
* Not used in Bully instance.
*/
@Override
protected void handleLeaderInvokeMessage() {}
@Override
protected void handleHeartbeatMessage(Message message) {}
@Override
protected void handleElectionMessage(Message message) {}
}

View File

@ -0,0 +1,117 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import com.iluwatar.leaderelection.AbstractMessageManager;
import com.iluwatar.leaderelection.Instance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageType;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Implementation of BullyMessageManager
*/
public class BullyMessageManager extends AbstractMessageManager {
/**
* Constructor of BullyMessageManager.
*/
public BullyMessageManager(Map<Integer, Instance> instanceMap) {
super(instanceMap);
}
/**
* Send heartbeat message to current leader instance to check the health.
* @param leaderId leaderID
* @return {@code true} if the leader is alive.
*/
@Override
public boolean sendHeartbeatMessage(int leaderId) {
Instance leaderInstance = instanceMap.get(leaderId);
boolean alive = leaderInstance.isAlive();
return alive;
}
/**
* Send election message to all the instances with smaller ID.
* @param currentId Instance ID of which sends this message.
* @param content Election message content.
* @return {@code true} if no alive instance has smaller ID, so that the election is accepted.
*/
@Override
public boolean sendElectionMessage(int currentId, String content) {
List<Integer> candidateList = findElectionCandidateInstanceList(currentId);
if (candidateList.isEmpty()) {
return true;
} else {
Message electionMessage = new Message(MessageType.ELECTION_INVOKE, "");
candidateList.stream()
.forEach((i) -> instanceMap.get(i).onMessage(electionMessage));
return false;
}
}
/**
* Send leader message to all the instances to notify the new leader.
* @param currentId Instance ID of which sends this message.
* @param leaderId Leader message content.
* @return {@code true} if the message is accepted.
*/
@Override
public boolean sendLeaderMessage(int currentId, int leaderId) {
Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId));
instanceMap.keySet()
.stream()
.filter((i) -> i != currentId)
.forEach((i) -> instanceMap.get(i).onMessage(leaderMessage));
return false;
}
/**
* Send heartbeat invoke message to the next instance.
* @param currentId Instance ID of which sends this message.
*/
@Override
public void sendHeartbeatInvokeMessage(int currentId) {
Instance nextInstance = this.findNextInstance(currentId);
Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, "");
nextInstance.onMessage(heartbeatInvokeMessage);
}
/**
* Find all the alive instances with smaller ID than current instance.
* @param currentId ID of current instance.
* @return ID list of all the candidate instance.
*/
private List<Integer> findElectionCandidateInstanceList(int currentId) {
return instanceMap.keySet()
.stream()
.filter((i) -> i < currentId && instanceMap.get(i).isAlive())
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,77 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import com.iluwatar.leaderelection.Instance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageManager;
import com.iluwatar.leaderelection.MessageType;
import java.util.HashMap;
import java.util.Map;
/**
* Example of how to use ring leader election. Initially 5 instances is created in the clould
* system, and the instance with ID 1 is set as leader. After the system is started stop the
* leader instance, and the new leader will be elected.
*/
public class RingApp {
/**
* Program entry point
*/
public static void main(String[] args) {
Map<Integer, Instance> instanceMap = new HashMap<>();
MessageManager messageManager = new RingMessageManager(instanceMap);
RingInstance instance1 = new RingInstance(messageManager, 1, 1);
RingInstance instance2 = new RingInstance(messageManager, 2, 1);
RingInstance instance3 = new RingInstance(messageManager, 3, 1);
RingInstance instance4 = new RingInstance(messageManager, 4, 1);
RingInstance instance5 = new RingInstance(messageManager, 5, 1);
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
instanceMap.put(4, instance4);
instanceMap.put(5, instance5);
instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, ""));
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
Thread thread3 = new Thread(instance3);
Thread thread4 = new Thread(instance4);
Thread thread5 = new Thread(instance5);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
instance1.setAlive(false);
}
}

View File

@ -0,0 +1,133 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import com.iluwatar.leaderelection.AbstractInstance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Implementation with token ring algorithm. The instances in the system are organized as a ring.
* Each instance should have a sequential id and the instance with smallest (or largest) id should
* be the initial leader. All the other instances send heartbeat message to leader periodically
* to check its health. If one certain instance finds the server done, it will send an election
* message to the next alive instance in the ring, which contains its own ID. Then the next instance
* add its ID into the message and pass it to the next. After all the alive instances' ID are add
* to the message, the message is send back to the first instance and it will choose the instance
* with smallest ID to be the new leader, and then send a leader message to other instances to
* inform the result.
*/
public class RingInstance extends AbstractInstance {
private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class);
/**
* Constructor of RingInstance.
*/
public RingInstance(MessageManager messageManager, int localId, int leaderId) {
super(messageManager, localId, leaderId);
}
/**
* Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat
* to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not,
* it will start the election process.
*/
@Override
protected void handleHeartbeatInvokeMessage() {
try {
boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId);
if (isLeaderAlive) {
LOGGER.info("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second.");
Thread.sleep(HEARTBEAT_INTERVAL);
messageManager.sendHeartbeatInvokeMessage(this.localId);
} else {
LOGGER.info("Instance " + localId + "- Leader is not alive. Start election.");
messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId));
}
} catch (InterruptedException e) {
LOGGER.info("Instance " + localId + "- Interrupted.");
}
}
/**
* Process election message. If the local ID is contained in the ID list, the instance will select the
* alive instance with smallest ID to be the new leader, and send the leader inform message. If not,
* it will add its local ID to the list and send the message to the next instance in the ring.
*/
@Override
protected void handleElectionMessage(Message message) {
String content = message.getContent();
LOGGER.info("Instance " + localId + " - Election Message: " + content);
List<Integer> candidateList =
Arrays.stream(content.trim().split(","))
.map(Integer::valueOf)
.sorted()
.collect(Collectors.toList());
if (candidateList.contains(localId)) {
int newLeaderId = candidateList.get(0);
LOGGER.info("Instance " + localId + " - New leader should be " + newLeaderId + ".");
messageManager.sendLeaderMessage(localId, newLeaderId);
} else {
content += "," + localId;
messageManager.sendElectionMessage(localId, content);
}
}
/**
* Process leader Message. The instance will set the leader ID to be the new one and send the message to
* the next instance until all the alive instance in the ring is informed.
*/
@Override
protected void handleLeaderMessage(Message message) {
int newLeaderId = Integer.valueOf(message.getContent());
if (this.leaderId != newLeaderId) {
LOGGER.info("Instance " + localId + " - Update leaderID");
this.leaderId = newLeaderId;
messageManager.sendLeaderMessage(localId, newLeaderId);
} else {
LOGGER.info("Instance " + localId + " - Leader update done. Start heartbeat.");
messageManager.sendHeartbeatInvokeMessage(localId);
}
}
/**
* Not used in Ring instance.
*/
@Override
protected void handleLeaderInvokeMessage() {}
@Override
protected void handleHeartbeatMessage(Message message) {}
@Override
protected void handleElectionInvokeMessage() {}
}

View File

@ -0,0 +1,96 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import com.iluwatar.leaderelection.AbstractMessageManager;
import com.iluwatar.leaderelection.Instance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageType;
import java.util.Map;
/**
* Implementation of RingMessageManager
*/
public class RingMessageManager extends AbstractMessageManager {
/**
* Constructor of RingMessageManager.
*/
public RingMessageManager(Map<Integer, Instance> instanceMap) {
super(instanceMap);
}
/**
* Send heartbeat message to current leader instance to check the health.
* @param leaderId leaderID
* @return {@code true} if the leader is alive.
*/
@Override
public boolean sendHeartbeatMessage(int leaderId) {
Instance leaderInstance = instanceMap.get(leaderId);
boolean alive = leaderInstance.isAlive();
return alive;
}
/**
* Send election message to the next instance.
* @param currentId currentID
* @param content list contains all the IDs of instances which have received this election message.
* @return {@code true} if the election message is accepted by the target instance.
*/
@Override
public boolean sendElectionMessage(int currentId, String content) {
Instance nextInstance = this.findNextInstance(currentId);
Message electionMessage = new Message(MessageType.ELECTION, content);
nextInstance.onMessage(electionMessage);
return true;
}
/**
* Send leader message to the next instance.
* @param currentId Instance ID of which sends this message.
* @param leaderId Leader message content.
* @return {@code true} if the leader message is accepted by the target instance.
*/
@Override
public boolean sendLeaderMessage(int currentId, int leaderId) {
Instance nextInstance = this.findNextInstance(currentId);
Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId));
nextInstance.onMessage(leaderMessage);
return true;
}
/**
* Send heartbeat invoke message to the next instance.
* @param currentId Instance ID of which sends this message.
*/
@Override
public void sendHeartbeatInvokeMessage(int currentId) {
Instance nextInstance = this.findNextInstance(currentId);
Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, "");
nextInstance.onMessage(heartbeatInvokeMessage);
}
}

View File

@ -0,0 +1,48 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Message test case.
*/
public class MessageTest {
@Test
public void testGetType() {
Message message = new Message(MessageType.HEARTBEAT, "");
assertEquals(MessageType.HEARTBEAT, message.getType());
}
@Test
public void testGetContent() {
String content = "test";
Message message = new Message(MessageType.HEARTBEAT, content);
assertEquals(content, message.getContent());
}
}

View File

@ -0,0 +1,39 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import org.junit.jupiter.api.Test;
/**
* BullyApp unit test.
*/
public class BullyAppTest {
@Test
public void test() {
String[] args = {};
BullyApp.main(args);
}
}

View File

@ -0,0 +1,151 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import com.iluwatar.leaderelection.*;
import com.iluwatar.leaderelection.ring.RingInstance;
import com.iluwatar.leaderelection.ring.RingMessageManager;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import static org.junit.jupiter.api.Assertions.*;
/**
* BullyMessageManager unit test.
*/
public class BullyMessageManagerTest {
@Test
public void testSendHeartbeatMessage() {
Instance instance1 = new BullyInstance(null, 1, 1);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
MessageManager messageManager = new BullyMessageManager(instanceMap);
assertTrue(messageManager.sendHeartbeatMessage(1));
}
@Test
public void testSendElectionMessageNotAccepted() {
try {
Instance instance1 = new BullyInstance(null, 1, 1);
Instance instance2 = new BullyInstance(null, 1, 2);
Instance instance3 = new BullyInstance(null, 1, 3);
Instance instance4 = new BullyInstance(null, 1, 4);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
instanceMap.put(4, instance4);
instance1.setAlive(false);
MessageManager messageManager = new BullyMessageManager(instanceMap);
boolean result = messageManager.sendElectionMessage(3, "3");
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message message2 = ((Queue<Message>) messageQueueField.get(instance2)).poll();
int instance4QueueSize = ((Queue<Message>) messageQueueField.get(instance4)).size();
Message expectedMessage = new Message(MessageType.ELECTION_INVOKE, "");
assertEquals(message2, expectedMessage);
assertEquals(instance4QueueSize, 0);
assertEquals(result, false);
} catch (IllegalAccessException | NoSuchFieldException e) {
fail("Error to access private field.");
}
}
@Test
public void testElectionMessageAccepted() {
Instance instance1 = new BullyInstance(null, 1, 1);
Instance instance2 = new BullyInstance(null, 1, 2);
Instance instance3 = new BullyInstance(null, 1, 3);
Instance instance4 = new BullyInstance(null, 1, 4);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
instanceMap.put(4, instance4);
instance1.setAlive(false);
MessageManager messageManager = new BullyMessageManager(instanceMap);
boolean result = messageManager.sendElectionMessage(2, "2");
assertEquals(result, true);
}
@Test
public void testSendLeaderMessage() {
try {
Instance instance1 = new BullyInstance(null, 1, 1);
Instance instance2 = new BullyInstance(null, 1, 2);
Instance instance3 = new BullyInstance(null, 1, 3);
Instance instance4 = new BullyInstance(null, 1, 4);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
instanceMap.put(4, instance4);
instance1.setAlive(false);
MessageManager messageManager = new BullyMessageManager(instanceMap);
messageManager.sendLeaderMessage(2, 2);
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message message3 = ((Queue<Message>) messageQueueField.get(instance3)).poll();
Message message4 = ((Queue<Message>) messageQueueField.get(instance4)).poll();
Message expectedMessage = new Message(MessageType.LEADER, "2");
assertEquals(message3, expectedMessage);
assertEquals(message4, expectedMessage);
} catch (IllegalAccessException | NoSuchFieldException e) {
fail("Error to access private field.");
}
}
@Test
public void testSendHeartbeatInvokeMessage() {
try {
Instance instance1 = new BullyInstance(null, 1, 1);
Instance instance2 = new BullyInstance(null, 1, 2);
Instance instance3 = new BullyInstance(null, 1, 3);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
MessageManager messageManager = new BullyMessageManager(instanceMap);
messageManager.sendHeartbeatInvokeMessage(2);
Message message = new Message(MessageType.HEARTBEAT_INVOKE, "");
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message messageSent = ((Queue<Message>) messageQueueField.get(instance3)).poll();
assertEquals(messageSent.getType(), message.getType());
assertEquals(messageSent.getContent(), message.getContent());
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Error to access private field.");
}
}
}

View File

@ -0,0 +1,78 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.bully;
import com.iluwatar.leaderelection.AbstractInstance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageType;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Queue;
import static org.junit.jupiter.api.Assertions.*;
/**
* BullyInstance unit test.
*/
public class BullyinstanceTest {
@Test
public void testOnMessage() {
try {
final BullyInstance bullyInstance = new BullyInstance(null, 1, 1);
Message bullyMessage = new Message(MessageType.HEARTBEAT, "");
bullyInstance.onMessage(bullyMessage);
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
assertEquals(bullyMessage, ((Queue<Message>) messageQueueField.get(bullyInstance)).poll());
} catch (IllegalAccessException | NoSuchFieldException e) {
fail("fail to access messasge queue.");
}
}
@Test
public void testIsAlive() {
try {
final BullyInstance bullyInstance = new BullyInstance(null, 1, 1);
Class instanceClass = AbstractInstance.class;
Field aliveField = instanceClass.getDeclaredField("alive");
aliveField.setAccessible(true);
aliveField.set(bullyInstance, false);
assertFalse(bullyInstance.isAlive());
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Fail to access field alive.");
}
}
@Test
public void testSetAlive() {
final BullyInstance bullyInstance = new BullyInstance(null, 1, 1);
bullyInstance.setAlive(false);
assertFalse(bullyInstance.isAlive());
}
}

View File

@ -0,0 +1,39 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import org.junit.jupiter.api.Test;
/**
* RingApp unit test.
*/
public class RingAppTest {
@Test
public void test() {
String[] args = {};
RingApp.main(args);
}
}

View File

@ -0,0 +1,76 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import com.iluwatar.leaderelection.AbstractInstance;
import com.iluwatar.leaderelection.Message;
import com.iluwatar.leaderelection.MessageType;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Queue;
import static org.junit.jupiter.api.Assertions.*;
/**
* RingInstance unit test.
*/
public class RingInstanceTest {
@Test
public void testOnMessage() {
try {
final RingInstance ringInstance = new RingInstance(null, 1, 1);
Message ringMessage = new Message(MessageType.HEARTBEAT, "");
ringInstance.onMessage(ringMessage);
Class ringInstanceClass = AbstractInstance.class;
Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
assertEquals(ringMessage, ((Queue<Message>) messageQueueField.get(ringInstance)).poll());
} catch (IllegalAccessException | NoSuchFieldException e) {
fail("fail to access messasge queue.");
}
}
@Test
public void testIsAlive() {
try {
final RingInstance ringInstance = new RingInstance(null, 1, 1);
Class ringInstanceClass = AbstractInstance.class;
Field aliveField = ringInstanceClass.getDeclaredField("alive");
aliveField.setAccessible(true);
aliveField.set(ringInstance, false);
assertFalse(ringInstance.isAlive());
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Fail to access field alive.");
}
}
@Test
public void testSetAlive() {
final RingInstance ringInstance = new RingInstance(null, 1, 1);
ringInstance.setAlive(false);
assertFalse(ringInstance.isAlive());
}
}

View File

@ -0,0 +1,123 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.leaderelection.ring;
import com.iluwatar.leaderelection.*;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import static org.junit.jupiter.api.Assertions.*;
/**
* RingMessageManager unit test.
*/
public class RingMessageManagerTest {
@Test
public void testSendHeartbeatMessage() {
Instance instance1 = new RingInstance(null, 1, 1);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
MessageManager messageManager = new RingMessageManager(instanceMap);
assertTrue(messageManager.sendHeartbeatMessage(1));
}
@Test
public void testSendElectionMessage() {
try {
Instance instance1 = new RingInstance(null, 1, 1);
Instance instance2 = new RingInstance(null, 1, 2);
Instance instance3 = new RingInstance(null, 1, 3);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
MessageManager messageManager = new RingMessageManager(instanceMap);
String messageContent = "2";
messageManager.sendElectionMessage(2, messageContent);
Message ringMessage = new Message(MessageType.ELECTION, messageContent);
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message ringMessageSent = ((Queue<Message>) messageQueueField.get(instance3)).poll();
assertEquals(ringMessageSent.getType(), ringMessage.getType());
assertEquals(ringMessageSent.getContent(), ringMessage.getContent());
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Error to access private field.");
}
}
@Test
public void testSendLeaderMessage() {
try {
Instance instance1 = new RingInstance(null, 1, 1);
Instance instance2 = new RingInstance(null, 1, 2);
Instance instance3 = new RingInstance(null, 1, 3);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
MessageManager messageManager = new RingMessageManager(instanceMap);
String messageContent = "3";
messageManager.sendLeaderMessage(2, 3);
Message ringMessage = new Message(MessageType.LEADER, messageContent);
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message ringMessageSent = ((Queue<Message>) messageQueueField.get(instance3)).poll();
assertEquals(ringMessageSent, ringMessage);
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Error to access private field.");
}
}
@Test
public void testSendHeartbeatInvokeMessage() {
try {
Instance instance1 = new RingInstance(null, 1, 1);
Instance instance2 = new RingInstance(null, 1, 2);
Instance instance3 = new RingInstance(null, 1, 3);
Map<Integer, Instance> instanceMap = new HashMap<>();
instanceMap.put(1, instance1);
instanceMap.put(2, instance2);
instanceMap.put(3, instance3);
MessageManager messageManager = new RingMessageManager(instanceMap);
messageManager.sendHeartbeatInvokeMessage(2);
Message ringMessage = new Message(MessageType.HEARTBEAT_INVOKE, "");
Class instanceClass = AbstractInstance.class;
Field messageQueueField = instanceClass.getDeclaredField("messageQueue");
messageQueueField.setAccessible(true);
Message ringMessageSent = ((Queue<Message>) messageQueueField.get(instance3)).poll();
assertEquals(ringMessageSent.getType(), ringMessage.getType());
assertEquals(ringMessageSent.getContent(), ringMessage.getContent());
} catch (NoSuchFieldException | IllegalAccessException e) {
fail("Error to access private field.");
}
}
}