/**
* Copyright (c) 2015 The original author or authors
*
* 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 org.reveno.atp.clustering.core.components;
import org.reveno.atp.clustering.api.Address;
import org.reveno.atp.clustering.api.Cluster;
import org.reveno.atp.clustering.api.ClusterView;
import org.reveno.atp.clustering.api.message.Message;
import org.reveno.atp.clustering.core.RevenoClusterConfiguration;
import org.reveno.atp.clustering.core.api.ClusterExecutor;
import org.reveno.atp.clustering.core.api.ElectionResult;
import org.reveno.atp.clustering.core.api.MessagesReceiver;
import org.reveno.atp.clustering.core.messages.VoteAck;
import org.reveno.atp.clustering.core.messages.VoteMessage;
import org.reveno.atp.clustering.util.Utils;
import org.reveno.atp.utils.BinaryUtils;
import org.reveno.atp.utils.RevenoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class MessagingMasterSlaveElector implements ClusterExecutor<ElectionResult, Void>, MessagesReceiver {
@Override
public ElectionResult execute(ClusterView currentView, Void context) {
LOG.info("Vote [view: {}]", currentView.viewId());
List<VoteMessage> answers = sendVoteNotifications(currentView);
if (answers.size() == 0 || !allAcked(currentView)) {
return revote(currentView);
} else {
boolean leader = answers.stream().allMatch(a -> config.priorityInCluster() > a.priority);
if (!leader && isAllSamePriority(answers)) {
leader = answers.stream().allMatch(a -> seed > a.seed);
}
LOG.trace("Vote finished [view: {}, leader: {}]", currentView.viewId(), leader);
return new ElectionResult(leader, false);
}
}
@Override
public void onMessage(Message message) {
if (message.type() == VoteMessage.TYPE) {
votes.put(message.address(), (VoteMessage) message);
} else if (message.type() == VoteAck.TYPE) {
acks.put(message.address(), ((VoteAck) message).viewId);
}
}
@Override
public Set<Integer> interestedTypes() {
return SUBSCRIPTION;
}
protected ElectionResult revote(ClusterView currentView) {
LOG.trace("Revote [view: {}; nodes: {}]", currentView.viewId(), currentView.members());
seed = generateSeed();
if (cluster.view().viewId() != currentView.viewId()) {
LOG.trace("Vote aborted [view: {}]", currentView.viewId());
return new ElectionResult(false, true);
} else {
return execute(currentView);
}
}
protected boolean allAcked(ClusterView view) {
cluster.gateway().send(view.members(), new VoteAck(view.viewId()), cluster.gateway().oob());
return RevenoUtils.waitFor(() -> acks.keySet().containsAll(view.members()) && acks.entrySet()
.stream()
.filter(kv -> view.members().contains(kv.getKey()))
.filter(kv -> view.viewId() == kv.getValue()).count() == view.members().size(),
config.revenoElectionTimeouts().ackTimeoutNanos());
}
protected List<VoteMessage> sendVoteNotifications(ClusterView view) {
VoteMessage message = new VoteMessage(view.viewId(), config.priorityInCluster(), seed);
cluster.gateway().send(view.members(), message, cluster.gateway().oob());
return waitForAnswers(view);
}
protected List<VoteMessage> waitForAnswers(ClusterView view) {
Predicate<VoteMessage> inView = m -> m.viewId == view.viewId() && view.members().contains(m.address());
Predicate<VoteMessage> diffSeed = m -> m.seed != seed;
if (!RevenoUtils.waitFor(() ->
votes.values().stream().filter(inView).filter(diffSeed).count() == view.members().size(),
config.revenoElectionTimeouts().voteTimeoutNanos())) {
return Collections.emptyList();
}
return votes.values().stream().filter(inView).filter(diffSeed).collect(Collectors.toList());
}
protected boolean isAllSamePriority(List<VoteMessage> answers) {
Map<Integer, Long> collect = answers.stream().collect(Collectors.groupingBy(o -> o.priority, Collectors.counting()));
return collect.size() == 1 && collect.keySet().iterator().next() == config.priorityInCluster();
}
public MessagingMasterSlaveElector(Cluster cluster, RevenoClusterConfiguration config) {
this.cluster = cluster;
this.config = config;
}
private long generateSeed() {
return BinaryUtils.bytesToLong(SecureRandom.getSeed(8));
}
protected Cluster cluster;
protected RevenoClusterConfiguration config;
protected Map<Address, VoteMessage> votes = new ConcurrentHashMap<>(1 << 6);
protected Map<Address, Long> acks = new ConcurrentHashMap<>(1 << 6);
protected long seed = generateSeed();
protected static final Logger LOG = LoggerFactory.getLogger(MessagingMasterSlaveElector.class);
protected static final Set<Integer> SUBSCRIPTION = new HashSet<Integer>() {{
add(VoteAck.TYPE);
add(VoteMessage.TYPE);
}};
}