/** * Copyright 2013 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.io.Files; import com.google.common.util.concurrent.AbstractService; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Guice; import com.google.protobuf.Service; import org.robotninjas.barge.proto.RaftProto; import org.robotninjas.barge.state.Raft; import org.robotninjas.barge.state.Raft.StateType; import org.robotninjas.barge.state.StateTransitionListener; import org.robotninjas.protobuf.netty.server.RpcServer; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; import java.io.File; import java.util.concurrent.ExecutionException; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Throwables.propagate; import static com.google.common.base.Throwables.propagateIfInstanceOf; @ThreadSafe @Immutable public class NettyRaftService extends AbstractService implements RaftService { private final RpcServer rpcServer; private final Raft ctx; @Inject NettyRaftService(@Nonnull RpcServer rpcServer, @Nonnull Raft ctx) { this.rpcServer = checkNotNull(rpcServer); this.ctx = checkNotNull(ctx); } @Override protected void doStart() { try { ctx.init().get(); configureRpcServer(); rpcServer.startAsync().awaitRunning(); notifyStarted(); } catch (Exception e) { notifyFailed(e); } } private void configureRpcServer() { RaftServiceEndpoint endpoint = new RaftServiceEndpoint(ctx); Service replicaService = RaftProto.RaftService.newReflectiveService(endpoint); rpcServer.registerService(replicaService); } @Override protected void doStop() { try { rpcServer.stopAsync().awaitTerminated(); ctx.stop(); notifyStopped(); } catch (Exception e) { notifyFailed(e); } } @Override public ListenableFuture<Object> commitAsync(final byte[] operation) throws RaftException { return ctx.commitOperation(operation); } @Override public Object commit(final byte[] operation) throws RaftException, InterruptedException { try { return commitAsync(operation).get(); } catch (ExecutionException e) { propagateIfInstanceOf(e.getCause(), NotLeaderException.class); propagateIfInstanceOf(e.getCause(), NoLeaderException.class); throw propagate(e.getCause()); } } public boolean isLeader() { return StateType.LEADER == ctx.type(); } public static Builder newBuilder(NettyClusterConfig config) { return new Builder(config); } private void addTransitionListener(StateTransitionListener listener) { ctx.addTransitionListener(listener); } public static class Builder { private static long TIMEOUT = 150; private final NettyClusterConfig config; private File logDir = Files.createTempDir(); private long timeout = TIMEOUT; private StateTransitionListener listener; protected Builder(NettyClusterConfig config) { this.config = config; } public Builder timeout(long timeout) { this.timeout = timeout; return this; } public Builder logDir(File logDir) { this.logDir = logDir; return this; } public NettyRaftService build(StateMachine stateMachine) { NettyRaftService nettyRaftService = Guice.createInjector( new NettyRaftModule(config, logDir, stateMachine, timeout)) .getInstance(NettyRaftService.class); if (listener != null) { nettyRaftService.addTransitionListener(listener); } return nettyRaftService; } public Builder transitionListener(StateTransitionListener listener) { this.listener = listener; return this; } } }