/** * Copyright 2013-2014 David Rusek <dave dot rusek at gmail dot com> * * 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.robotninjas.barge; import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.junit.rules.ExternalResource; import org.robotninjas.barge.state.Raft; import static org.robotninjas.barge.state.Raft.StateType; import org.robotninjas.barge.state.StateTransitionListener; import org.robotninjas.barge.utils.Prober; import java.io.File; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class GroupOfCounters extends ExternalResource implements StateTransitionListener { private final List<NettyReplica> replicas; private final List<SimpleCounterMachine> counters; private final File target; private final Map<Raft, StateType> states = Maps.newConcurrentMap(); public GroupOfCounters(int numberOfReplicas, File target) { this.target = target; replicas = Lists.newArrayList(); counters = Lists.newArrayList(); for (int i = 10001; i <= (10000 + numberOfReplicas); i++) { replicas.add(NettyReplica.fromString("localhost:" + i)); counters.add(new SimpleCounterMachine(i - 10001, replicas, this)); } } @Override protected void before() throws Throwable { for (SimpleCounterMachine counter : counters) { counter.makeLogDirectory(target); counter.startRaft(); } } @Override protected void after() { for (SimpleCounterMachine counter : counters) { counter.stop(); counter.deleteLogDirectory(); } } public void commitToLeader(byte[] bytes) throws RaftException, InterruptedException { getLeader().get().commit(bytes); } private Optional<SimpleCounterMachine> getLeader() { for (SimpleCounterMachine counter : counters) { if (counter.isLeader()) { return Optional.of(counter); } } return Optional.absent(); } /** * Wait for all {@link SimpleCounterMachine} in the cluster to reach a consensus value. * * @param target expected value for each machine' counter. * @param timeout timeout in ms. Timeout is evaluated per instance of counter within the cluster. */ public void waitAllToReachValue(int target, long timeout) { for (SimpleCounterMachine counter : counters) { counter.waitForValue(target, timeout); } } void waitForLeaderElection() throws InterruptedException { new Prober(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return thereIsOneLeader(); } }).probe(10000); } private Boolean thereIsOneLeader() { int numberOfLeaders = 0; int numberOfFollowers = 0; for (StateType stateType : states.values()) { switch (stateType) { case LEADER: numberOfLeaders++; break; case FOLLOWER: numberOfFollowers++; break; } } return (numberOfLeaders == 1) && ((numberOfFollowers + numberOfLeaders) == replicas.size()); } @Override public void changeState(@Nonnull Raft context, @Nullable StateType from, @Nonnull StateType to) { states.put(context, to); } @Override public void invalidTransition(@Nonnull Raft context, @Nonnull StateType actual, @Nullable StateType expected) { // IGNORED } @Override public void stop(@Nonnull Raft raft) { // IGNORED } }