package org.rakam.kume;
import org.rakam.kume.transport.Operation;
import org.rakam.kume.transport.OperationContext;
import org.rakam.kume.util.Tuple;
import org.rakam.kume.transport.Request;
import org.rakam.kume.util.FutureUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import static com.google.common.base.Preconditions.checkState;
public class ClusterCheckAndMergeOperation implements Operation<InternalService>
{
final static Logger LOGGER = LoggerFactory.getLogger(ClusterCheckAndMergeOperation.class);
@Override
public void run(InternalService service, OperationContext<Void> ctx) {
Cluster cluster = service.cluster;
Set<Member> clusterMembers = cluster.getMembers();
Member masterNode = cluster.getMaster();
checkState(masterNode.equals(cluster.getLocalMember()), "only master node must execute ClusterCheckAndMergeOperation.");
Member sender = ctx.getSender();
if (clusterMembers.contains(sender))
return;
LOGGER.trace("got cluster check and merge request from a server who is not in this cluster");
MemberChannel channel;
try {
channel = cluster.getTransport().connect(ctx.getSender());
} catch (InterruptedException e) {
LOGGER.trace("a server send me join request from udp but i can't connect him. ignoring");
return;
}
LOGGER.trace("connected to the new node, now getting cluster information");
cluster.clusterConnection.put(ctx.getSender(), channel);
final Tuple<Set<Member>, Long> clusterStatus;
try {
clusterStatus = cluster.internalBus
.tryAskUntilDone(ctx.getSender(), new GetInformationFromDiscoveredCluster(), 5, Tuple.class)
.join();
LOGGER.trace("got answer from discovered cluster: " + clusterStatus._1.size()
+ "members, last commit index: "+clusterStatus._2);
} catch (CompletionException e) {
cluster.clusterConnection.remove(ctx.getSender());
return;
}
Set<Member> otherMembers = new HashSet<>(clusterStatus._1);
Set<Member> members = cluster.getMembers();
otherMembers.removeAll(members);
synchronized (cluster) {
int otherSize = otherMembers.size();
int mySize = cluster.getMembers().size();
long myCommitIndex = cluster.getLastCommitIndex().get();
Long otherCommitIndex = clusterStatus._2;
if (otherSize > mySize || (otherSize == mySize && myCommitIndex < otherCommitIndex)) {
LOGGER.trace("they will eventually add me");
return;
}
synchronized (cluster) {
LOGGER.trace("they must join me, my cluster is bigger than theirs");
for (Member otherMember : otherMembers) {
MemberChannel memberChannel;
try {
memberChannel = cluster.getTransport().connect(otherMember);
} catch (InterruptedException e) {
otherMembers.remove(otherMember);
continue;
}
cluster.clusterConnection.put(otherMember, memberChannel);
}
FutureUtil.MultipleFutureListener f = new FutureUtil.MultipleFutureListener(otherMembers.size());
LOGGER.trace("asking new members to join our party.");
for (Member otherMember : otherMembers) {
CompletableFuture<Void> ask = cluster.internalBus
.tryAskUntilDone(otherMember, new JoinThisClusterRequest(cluster.getMembers(), masterNode), 5);
ask.whenComplete((result, ex) -> {
if (ex != null) {
otherMembers.remove(otherMember);
cluster.clusterConnection.remove(otherMember);
LOGGER.trace(otherMember + " was a member of the new cluster but since I (master) couldn't connect it," +
" it will be ignored.");
} else {
LOGGER.trace(otherMember + " successfully connected to master");
}
f.increment();
});
}
LOGGER.trace("waiting responses from new members");
f.get().join();
LOGGER.trace(otherMembers.size() + " members is added to the cluster");
LOGGER.trace("replicating information about new members to cluster members");
cluster.internalBus.replicateSafely(new MembersJoinedRequest(otherMembers)).join();
LOGGER.trace("all members in other cluster successfully added into this cluster");
}
}
}
public static class JoinThisClusterRequest implements Request<InternalService, Void> {
final Set<Member> members;
final Member masterNode;
public JoinThisClusterRequest(Set<Member> members, Member masterNode) {
this.members = members;
this.masterNode = masterNode;
}
@Override
public void run(InternalService service, OperationContext<Void> ctx) {
LOGGER.trace("someone wants me in his cluster, i will join her party.");
// service.cluster.changeCluster(members, masterNode, false);
ctx.reply(null);
}
}
public static class MembersJoinedRequest implements Request<InternalService, Boolean> {
final Set<Member> members;
public MembersJoinedRequest(Set<Member> members) {
this.members = members;
}
@Override
public void run(InternalService service, OperationContext<Boolean> ctx) {
LOGGER.trace("there are new members in the cluster. welcoming them.");
// if(members.size() > 1)
// service.cluster.getTransport().aad(members);
// else
// if(members.size() == 1)
// service.cluster.addMemberInternal(members.iterator().next());
ctx.reply(null);
}
}
public static class GetInformationFromDiscoveredCluster implements Request<InternalService, Tuple> {
@Override
public void run(InternalService service, OperationContext<Tuple> ctx) {
Cluster cluster1 = service.cluster;
Tuple<Set, Long> obj = new Tuple<>(cluster1.getMembers(), cluster1.getLastCommitIndex().get());
LOGGER.trace("i was asked my cluster status. here it is: " +
obj._1.size() + "members, last commit index: " + obj._2);
ctx.reply(obj);
}
}
}