├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── barge-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── robotninjas │ │ └── barge │ │ ├── BatchExecutor.java │ │ ├── ClusterConfig.java │ │ ├── NoLeaderException.java │ │ ├── NotLeaderException.java │ │ ├── RaftCoreModule.java │ │ ├── RaftException.java │ │ ├── RaftExecutor.java │ │ ├── Replica.java │ │ ├── StateMachine.java │ │ ├── api │ │ ├── Append.java │ │ ├── AppendEntries.java │ │ ├── AppendEntriesResponse.java │ │ ├── Commit.java │ │ ├── Entry.java │ │ ├── JournalEntry.java │ │ ├── Membership.java │ │ ├── RequestVote.java │ │ ├── RequestVoteResponse.java │ │ ├── Snapshot.java │ │ ├── Term.java │ │ ├── Vote.java │ │ └── package-info.java │ │ ├── log │ │ ├── GetEntriesResult.java │ │ ├── LogModule.java │ │ ├── RaftJournal.java │ │ ├── RaftLog.java │ │ ├── StateExecutor.java │ │ └── StateMachineProxy.java │ │ ├── rpc │ │ ├── Client.java │ │ ├── RaftClient.java │ │ └── RaftClientProvider.java │ │ ├── state │ │ ├── AbstractListenersModule.java │ │ ├── BaseState.java │ │ ├── Candidate.java │ │ ├── DeadlineTimer.java │ │ ├── DefaultStateFactory.java │ │ ├── ElectionTimeout.java │ │ ├── Follower.java │ │ ├── Leader.java │ │ ├── LogListener.java │ │ ├── MajorityCollector.java │ │ ├── Raft.java │ │ ├── RaftPredicates.java │ │ ├── RaftProtocolListener.java │ │ ├── RaftStateContext.java │ │ ├── ReplicaManager.java │ │ ├── ReplicaManagerFactory.java │ │ ├── ReplicaState.java │ │ ├── Start.java │ │ ├── State.java │ │ ├── StateFactory.java │ │ ├── StateModule.java │ │ ├── StateTransitionListener.java │ │ └── Stopped.java │ │ └── utils │ │ ├── Files.java │ │ └── Prober.java │ └── test │ ├── java │ └── org │ │ └── robotninjas │ │ └── barge │ │ ├── ClusterConfigStub.java │ │ ├── api │ │ └── JournalEntryTest.java │ │ └── state │ │ ├── BaseStateTest.java │ │ ├── CandidateTest.java │ │ ├── MajorityCollectorTest.java │ │ ├── RaftStateContextTest.java │ │ └── ReplicaManagerTest.java │ └── resources │ └── logback-test.xml ├── barge-jax-rs ├── barge.conf ├── init.sh ├── pack.sh ├── pom.xml ├── send.sh └── src │ ├── main │ ├── java │ │ ├── org │ │ │ └── robotninjas │ │ │ │ └── barge │ │ │ │ └── jaxrs │ │ │ │ ├── BargeResource.java │ │ │ │ ├── HttpClusterConfig.java │ │ │ │ ├── HttpReplica.java │ │ │ │ ├── Jackson.java │ │ │ │ ├── JaxRsRaftModule.java │ │ │ │ ├── RaftApplication.java │ │ │ │ ├── RaftHttpException.java │ │ │ │ ├── RaftJdkServer.java │ │ │ │ ├── RaftServer.java │ │ │ │ ├── client │ │ │ │ ├── BargeJaxRsClient.java │ │ │ │ └── HttpRaftClientProvider.java │ │ │ │ └── ws │ │ │ │ ├── EventServlet.java │ │ │ │ ├── EventSocket.java │ │ │ │ ├── Listener.java │ │ │ │ ├── RaftJettyServer.java │ │ │ │ ├── SessionToListener.java │ │ │ │ ├── WsEventListener.java │ │ │ │ ├── WsEventListenersModule.java │ │ │ │ ├── WsMessage.java │ │ │ │ └── WsMessages.java │ │ └── pack.java │ └── resources │ │ ├── logback.xml │ │ └── script-header │ └── test │ ├── java │ └── org │ │ └── robotninjas │ │ └── barge │ │ └── jaxrs │ │ ├── BargeResourceTest.java │ │ ├── HttpClusterConfigTest.java │ │ ├── HttpReplicaTest.java │ │ ├── JdkHttpDeploymentTest.java │ │ ├── JettyDeploymentTest.java │ │ ├── Logs.java │ │ ├── Model.java │ │ ├── MuteJUL.java │ │ ├── ServerTest.java │ │ ├── client │ │ ├── BargeJaxRsClientTest.java │ │ └── HttpRaftClientProviderTest.java │ │ └── ws │ │ ├── EventSocketTest.java │ │ ├── RaftJettyServerTest.java │ │ └── WsEventListenerTest.java │ └── resources │ └── marker ├── barge-rpc-proto ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── robotninjas │ │ │ └── barge │ │ │ ├── NettyClusterConfig.java │ │ │ ├── NettyRaftModule.java │ │ │ ├── NettyRaftService.java │ │ │ ├── NettyReplica.java │ │ │ ├── ProtoUtils.java │ │ │ ├── RaftProtoRpcModule.java │ │ │ ├── RaftService.java │ │ │ ├── RaftServiceEndpoint.java │ │ │ ├── proto │ │ │ ├── LogProto.java │ │ │ ├── RaftEntry.java │ │ │ └── RaftProto.java │ │ │ └── rpc │ │ │ ├── ProtoRpcRaftClient.java │ │ │ ├── ProtoRpcRaftClientProvider.java │ │ │ ├── RpcChannelFactory.java │ │ │ └── RpcModule.java │ └── proto │ │ ├── entry.proto │ │ ├── log.proto │ │ └── raft.proto │ └── test │ ├── java │ └── org │ │ └── robotninjas │ │ └── barge │ │ ├── GroupOfCounters.java │ │ ├── NettyRaftServiceTest.java │ │ ├── ProtoUtilsTest.java │ │ ├── SimpleCounterMachine.java │ │ └── StartStopTest.java │ └── resources │ └── logback-test.xml ├── barge-tools ├── pom.xml └── src │ └── main │ ├── assembly │ └── assembly.xml │ ├── bin │ └── logtool.sh │ └── java │ └── org │ └── robotninjas │ └── barge │ └── tools │ └── LogTool.java ├── barge-ui ├── README.md ├── css │ ├── bootstrap │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ └── default.css │ ├── pure-release-0.5.0 │ │ ├── HISTORY.md │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── base-context-min.css │ │ ├── base-context.css │ │ ├── base-min.css │ │ ├── base.css │ │ ├── bower.json │ │ ├── buttons-core-min.css │ │ ├── buttons-core.css │ │ ├── buttons-min.css │ │ ├── buttons.css │ │ ├── forms-min.css │ │ ├── forms-nr-min.css │ │ ├── forms-nr.css │ │ ├── forms.css │ │ ├── grids-core-min.css │ │ ├── grids-core.css │ │ ├── grids-min.css │ │ ├── grids-responsive-min.css │ │ ├── grids-responsive-old-ie-min.css │ │ ├── grids-responsive-old-ie.css │ │ ├── grids-responsive.css │ │ ├── grids-units-min.css │ │ ├── grids-units.css │ │ ├── grids.css │ │ ├── menus-core-min.css │ │ ├── menus-core.css │ │ ├── menus-min.css │ │ ├── menus-nr-min.css │ │ ├── menus-nr.css │ │ ├── menus-paginator-min.css │ │ ├── menus-paginator.css │ │ ├── menus.css │ │ ├── pure-min.css │ │ ├── pure-nr-min.css │ │ ├── pure-nr.css │ │ ├── pure.css │ │ ├── tables-min.css │ │ └── tables.css │ └── styles.css ├── index.html ├── js │ └── react.min.js ├── project.clj └── src │ └── barge │ ├── core.cljs │ └── messaging.cljs ├── pom.xml └── travis-settings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | *.iml 4 | *.ipr 5 | *.iws 6 | .gradle 7 | build 8 | out 9 | dependency-reduced-pom.xml 10 | 11 | # Eclipse stuff 12 | .classpath 13 | .project 14 | .settings/ 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | script: "mvn package -Ptravis --settings travis-settings.xml" 3 | jdk: 4 | - openjdk8 5 | - oraclejdk8 6 | 7 | env: 8 | global: 9 | - secure: "FgMwSRz94bofID4qCS4lHszXVP98bymZ7sPexzPqOGugFvLDAnmHYtp70TiW\nCkvbUw6Y8BRfMzDLciI+O+6TCZz9luXpKTg9bt5y7bM7COimDLqvVkmL3qxk\n9XEJoI5yUua5aWi0VXEEQ+xbVILVRKWXLtZVzKPKCf4z/vHwXEA=" 10 | - secure: "XDeAS31a70hLNKxhiQUcR2RlN/fVPg8dg4FxbQTJ8ZjVw7x5kMNqFJ0J+rho\nr4gFPxo7urz0BZL3qTQvyF6KisLF1bLdYOjTA+jQ43lFgrpp1fgc5/OSpa/4\nQ6UoEM4gdADUABPMh1bdG8WGQiHrdP5ItyLXvgrJ144KQUX1hf0=" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mgodave/barge.png)](https://travis-ci.org/mgodave/barge) 2 | 3 | barge (ALPHA) 4 | ===== 5 | 6 | An implementation of the [Raft Consensus Protocol][1]. 7 | 8 | [1]: http://raftconsensus.github.io/ 9 | [2]: irc://chat.freenode.net/barge 10 | 11 | Todo 12 | ==== 13 | Barge is still a work in progress, two major missing features are: 14 | 15 | * Log Compaction (alpha2) 16 | * Dynamic Membership (alpha2) 17 | 18 | Roadmap 19 | ======= 20 | Barge is currently at 0.1.0-alpha1. I intend to release an alpha2 when the library is feature complete from the standpoint of the paper. Interest and involvement will determine how the library progresses past the alpha stage. 21 | 22 | Get It 23 | ====== 24 | 25 | ```xml 26 | 27 | org.robotninjas.barge 28 | barge-core 29 | 0.1.0-alpha1 30 | 31 | ``` 32 | 33 | Use It 34 | ====== 35 | 36 | ```java 37 | public class Test implements StateMachine { 38 | 39 | @Override 40 | public void applyOperation(@Nonnull ByteBuffer entry) { 41 | System.out.println(entry.getLong()); 42 | } 43 | 44 | public static void main(String... args) throws Exception { 45 | 46 | ClusterConfig config = ClusterConfig.from( 47 | Replica.fromString("localhost:10000"), // local 48 | Replica.fromString("localhost:10001"), // remote 49 | Replica.fromString("localhost:10002") // remote 50 | ); 51 | 52 | File logDir = new File(args[0]); 53 | logDir.mkdir(); 54 | 55 | // configure the service 56 | RaftService raft = 57 | RaftService.newBuilder(config) 58 | .logDir(logDir) 59 | .timeout(300) 60 | .build(new Test()); 61 | 62 | // start this replica 63 | raft.startAsync().awaitRunning(); 64 | 65 | // let's commit some things 66 | for (int i = 0; i < 10; i++) { 67 | raft.commit(new byte[] {'O', '_', 'o'}).get(); 68 | } 69 | 70 | } 71 | 72 | } 73 | 74 | 75 | ``` 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/BatchExecutor.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import com.google.common.collect.Maps; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | import org.jetlang.core.BatchExecutorImpl; 7 | import org.jetlang.core.EventReader; 8 | import org.slf4j.MDC; 9 | 10 | class BatchExecutor extends BatchExecutorImpl { 11 | 12 | Map contextMap = Maps.newHashMap(); 13 | 14 | @Override 15 | public void execute(EventReader toExecute) { 16 | 17 | Optional oldContext = Optional.ofNullable(MDC.getCopyOfContextMap()); 18 | MDC.setContextMap(contextMap); 19 | 20 | super.execute(toExecute); 21 | 22 | contextMap = MDC.getCopyOfContextMap(); 23 | oldContext.ifPresent(MDC::setContextMap); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/ClusterConfig.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | public interface ClusterConfig { 4 | 5 | Replica local(); 6 | 7 | Iterable remote(); 8 | 9 | Replica getReplica(String info); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/NoLeaderException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge; 18 | 19 | import javax.annotation.concurrent.Immutable; 20 | 21 | @Immutable 22 | public class NoLeaderException extends RaftException { 23 | 24 | public NoLeaderException() { 25 | } 26 | 27 | public NoLeaderException(String s) { 28 | super(s); 29 | } 30 | 31 | public NoLeaderException(String s, Throwable throwable) { 32 | super(s, throwable); 33 | } 34 | 35 | public NoLeaderException(Throwable throwable) { 36 | super(throwable); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/NotLeaderException.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | public class NotLeaderException extends RaftException { 4 | 5 | private final Replica leader; 6 | 7 | public NotLeaderException(Replica leader) { 8 | this.leader = leader; 9 | } 10 | 11 | public Replica getLeader() { 12 | return leader; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/RaftException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge; 18 | 19 | public class RaftException extends Exception { 20 | 21 | public RaftException() { 22 | } 23 | 24 | public RaftException(String s) { 25 | super(s); 26 | } 27 | 28 | public RaftException(String s, Throwable throwable) { 29 | super(s, throwable); 30 | } 31 | 32 | public RaftException(Throwable throwable) { 33 | super(throwable); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/RaftExecutor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge; 18 | 19 | import static java.lang.annotation.ElementType.FIELD; 20 | import static java.lang.annotation.ElementType.METHOD; 21 | import static java.lang.annotation.ElementType.PARAMETER; 22 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 23 | 24 | import com.google.inject.BindingAnnotation; 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.Target; 27 | 28 | @BindingAnnotation 29 | @Target({FIELD, PARAMETER, METHOD}) 30 | @Retention(RUNTIME) 31 | public @interface RaftExecutor { 32 | } 33 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/Replica.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | public interface Replica { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/StateMachine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge; 18 | 19 | import java.nio.ByteBuffer; 20 | import javax.annotation.Nonnull; 21 | 22 | public interface StateMachine { 23 | 24 | Object applyOperation(@Nonnull ByteBuffer entry); 25 | 26 | // void takeSnapshot(@Nonnull OutputStream snapshot); 27 | // 28 | // void installSnapshot(@Nonnull InputStream snapshot); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Append.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import com.google.common.base.Objects; 20 | import java.io.Serializable; 21 | import javax.annotation.concurrent.Immutable; 22 | 23 | /** 24 | */ 25 | @Immutable 26 | public class Append implements Serializable { 27 | 28 | private final long index; 29 | private final Entry entry; 30 | 31 | public Append(long index, Entry entry) { 32 | this.index = index; 33 | this.entry = entry; 34 | } 35 | 36 | public long getIndex() { 37 | return index; 38 | } 39 | 40 | public Entry getEntry() { 41 | return entry; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hashCode(entry) * 37 + (int) index; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | if (!(obj instanceof Append)) 52 | return false; 53 | 54 | Append that = (Append) obj; 55 | 56 | return index == that.index && Objects.equal(entry, that.entry); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return MoreObjects.toStringHelper(this) 62 | .add("index", index) 63 | .add("entry", entry) 64 | .toString(); 65 | } 66 | 67 | public static Builder newBuilder() { 68 | return new Builder(); 69 | } 70 | 71 | public static class Builder { 72 | private Entry entry; 73 | private long index; 74 | 75 | public Builder setEntry(Entry entry) { 76 | this.entry = entry; 77 | return this; 78 | } 79 | 80 | public Entry getEntry() { 81 | return entry; 82 | } 83 | 84 | public Builder setIndex(long index) { 85 | this.index = index; 86 | return this; 87 | } 88 | 89 | public Append build() { 90 | return new Append(index, entry); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/AppendEntriesResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import javax.annotation.concurrent.Immutable; 20 | 21 | /** 22 | */ 23 | @Immutable 24 | public class AppendEntriesResponse { 25 | 26 | private final long term; 27 | private final boolean success; 28 | private final long lastLogIndex; 29 | 30 | public AppendEntriesResponse(long term, boolean success, long lastLogIndex) { 31 | this.term = term; 32 | this.success = success; 33 | this.lastLogIndex = lastLogIndex; 34 | } 35 | 36 | public static Builder newBuilder() { 37 | return new Builder(); 38 | } 39 | 40 | public boolean getSuccess() { 41 | return success; 42 | } 43 | 44 | public long getTerm() { 45 | return term; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (!(o instanceof AppendEntriesResponse)) return false; 51 | 52 | AppendEntriesResponse that = (AppendEntriesResponse) o; 53 | 54 | return lastLogIndex == that.lastLogIndex 55 | && success == that.success 56 | && term == that.term; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | int result = (int) (term ^ (term >>> 32)); 62 | result = 31 * result + (success ? 1 : 0); 63 | result = 31 * result + (int) (lastLogIndex ^ (lastLogIndex >>> 32)); 64 | return result; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return MoreObjects.toStringHelper(this) 70 | .add("term", term) 71 | .add("success", success) 72 | .add("lastLogIndex", lastLogIndex) 73 | .toString(); 74 | } 75 | 76 | public long getLastLogIndex() { 77 | return lastLogIndex; 78 | } 79 | 80 | public static class Builder { 81 | private long term; 82 | private boolean success; 83 | private long lastLogIndex; 84 | 85 | public Builder setTerm(long term) { 86 | this.term = term; 87 | return this; 88 | } 89 | 90 | public Builder setSuccess(boolean success) { 91 | this.success = success; 92 | return this; 93 | } 94 | 95 | public Builder setLastLogIndex(long lastLogIndex) { 96 | this.lastLogIndex = lastLogIndex; 97 | return this; 98 | } 99 | 100 | public AppendEntriesResponse build() { 101 | return new AppendEntriesResponse(term,success,lastLogIndex); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Commit.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import java.io.Serializable; 20 | import javax.annotation.concurrent.Immutable; 21 | 22 | /** 23 | */ 24 | @Immutable 25 | public class Commit implements Serializable { 26 | 27 | private final long index; 28 | 29 | public Commit(long index) { 30 | this.index = index; 31 | } 32 | 33 | public static Builder newBuilder() { 34 | return new Builder(); 35 | } 36 | 37 | public long getIndex() { 38 | return index; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return (int) index; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object obj) { 48 | return obj instanceof Commit && index == ((Commit) obj).index; 49 | 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return MoreObjects.toStringHelper(this) 55 | .add("index", index) 56 | .toString(); 57 | } 58 | 59 | public static class Builder { 60 | private long index; 61 | 62 | public Builder setIndex(long index) { 63 | this.index = index; 64 | return this; 65 | } 66 | 67 | public Commit build() { 68 | return new Commit(index); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Entry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import java.io.Serializable; 20 | import java.util.Arrays; 21 | import javax.annotation.concurrent.Immutable; 22 | 23 | /** 24 | */ 25 | @Immutable 26 | public class Entry implements Serializable { 27 | 28 | private final byte[] command; 29 | private final long term; 30 | 31 | public Entry(byte[] command, long term) { 32 | this.command = Arrays.copyOf(command,command.length); 33 | this.term = term; 34 | } 35 | 36 | public long getTerm() { 37 | return term; 38 | } 39 | 40 | public byte[] getCommand() { 41 | return command; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Arrays.hashCode(command) * 37 + (int) term; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | if(!(obj instanceof Entry)) 52 | return false; 53 | 54 | Entry that = (Entry)obj; 55 | return Arrays.equals(command,that.command) && term == that.term; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return MoreObjects.toStringHelper(this) 61 | .add("command", command) 62 | .add("term", term) 63 | .toString(); 64 | } 65 | 66 | public static Builder newBuilder() { 67 | return new Builder(); 68 | } 69 | 70 | public static class Builder { 71 | 72 | private byte[] command; 73 | private long term; 74 | 75 | public Builder setCommand(byte[] command) { 76 | this.command = command; 77 | return this; 78 | } 79 | 80 | public Builder setTerm(long term) { 81 | this.term = term; 82 | return this; 83 | } 84 | 85 | public Entry build() { 86 | return new Entry(command,term) 87 | ; } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Membership.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | */ 22 | public class Membership { 23 | private List membersList; 24 | 25 | public List getMembersList() { 26 | return membersList; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/RequestVoteResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import javax.annotation.concurrent.Immutable; 20 | 21 | /** 22 | */ 23 | @Immutable 24 | public class RequestVoteResponse { 25 | 26 | private final long term; 27 | private final boolean voteGranted; 28 | 29 | public RequestVoteResponse(long term, boolean voteGranted) { 30 | this.term = term; 31 | this.voteGranted = voteGranted; 32 | } 33 | 34 | public static Builder newBuilder() { 35 | return new Builder(); 36 | } 37 | 38 | public boolean getVoteGranted() { 39 | return voteGranted; 40 | } 41 | 42 | public long getTerm() { 43 | return term; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (!(o instanceof RequestVoteResponse)) return false; 49 | 50 | RequestVoteResponse that = (RequestVoteResponse) o; 51 | 52 | return term == that.term && voteGranted == that.voteGranted; 53 | 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | int result = (int) (term ^ (term >>> 32)); 59 | result = 31 * result + (voteGranted ? 1 : 0); 60 | return result; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return MoreObjects.toStringHelper(this) 66 | .add("term", term) 67 | .add("voteGranted", voteGranted) 68 | .toString(); 69 | } 70 | 71 | public static class Builder { 72 | private long term; 73 | private boolean voteGranted; 74 | 75 | public Builder setTerm(long term) { 76 | this.term = term; 77 | return this; 78 | } 79 | 80 | public Builder setVoteGranted(boolean voteGranted) { 81 | this.voteGranted = voteGranted; 82 | return this; 83 | } 84 | 85 | public RequestVoteResponse build() { 86 | return new RequestVoteResponse(term, voteGranted); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Snapshot.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import com.google.common.base.Objects; 20 | import java.io.Serializable; 21 | import javax.annotation.concurrent.Immutable; 22 | 23 | /** 24 | */ 25 | @Immutable 26 | public class Snapshot implements Serializable { 27 | 28 | private final long lastIncludedIndex; 29 | private final long lastIncludedTerm; 30 | private final String snapshotFile; 31 | 32 | public Snapshot(long lastIncludedIndex, long lastIncludedTerm, String snapshotFile) { 33 | this.lastIncludedIndex = lastIncludedIndex; 34 | this.lastIncludedTerm = lastIncludedTerm; 35 | this.snapshotFile = snapshotFile; 36 | } 37 | 38 | public static Builder newBuilder() { 39 | return new Builder(); 40 | } 41 | 42 | public long getLastIncludedIndex() { 43 | return lastIncludedIndex; 44 | } 45 | 46 | public long getLastIncludedTerm() { 47 | return lastIncludedTerm; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | int result = (int) (lastIncludedIndex ^ (lastIncludedIndex >>> 32)); 53 | result = 31 * result + (int) (lastIncludedTerm ^ (lastIncludedTerm >>> 32)); 54 | result = 31 * result + (snapshotFile != null ? snapshotFile.hashCode() : 0); 55 | return result; 56 | } 57 | 58 | @Override 59 | public boolean equals(Object obj) { 60 | if (!(obj instanceof Snapshot)) 61 | return false; 62 | 63 | Snapshot that = (Snapshot) obj; 64 | 65 | return lastIncludedIndex == that.lastIncludedIndex 66 | && lastIncludedTerm == that.lastIncludedTerm 67 | && Objects.equal(snapshotFile, that.snapshotFile); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return MoreObjects.toStringHelper(this) 73 | .add("lastIncludedIndex", lastIncludedIndex) 74 | .add("lastIncludedTerm", lastIncludedTerm) 75 | .add("snapshotFile", snapshotFile) 76 | .toString(); 77 | } 78 | 79 | public static class Builder { 80 | private long lastIncludedIndex; 81 | private long lastIncludedTerm; 82 | private String snapshotFile; 83 | 84 | public Builder setLastIncludedIndex(long lastIncludedIndex) { 85 | this.lastIncludedIndex = lastIncludedIndex; 86 | return this; 87 | } 88 | 89 | public Builder setLastIncludedTerm(long lastIncludedTerm) { 90 | this.lastIncludedTerm = lastIncludedTerm; 91 | return this; 92 | } 93 | 94 | public Builder setSnapshotFile(String snapshotFile) { 95 | this.snapshotFile = snapshotFile; 96 | return this; 97 | } 98 | 99 | public Snapshot build() { 100 | return new Snapshot(lastIncludedIndex, lastIncludedTerm, snapshotFile); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Term.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import java.io.Serializable; 20 | import javax.annotation.concurrent.Immutable; 21 | 22 | /** 23 | */ 24 | @Immutable 25 | public class Term implements Serializable { 26 | 27 | private final long term; 28 | 29 | public Term(long term) { 30 | this.term = term; 31 | } 32 | 33 | public static Builder newBuilder() { 34 | return new Builder(); 35 | } 36 | 37 | public long getTerm() { 38 | return term; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | return o instanceof Term && term == ((Term) o).term; 44 | 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return (int) (term ^ (term >>> 32)); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return MoreObjects.toStringHelper(this) 55 | .add("term", term) 56 | .toString(); 57 | } 58 | 59 | public static class Builder { 60 | private long term; 61 | 62 | public Builder setTerm(long term) { 63 | this.term = term; 64 | return this; 65 | } 66 | 67 | public Term build() { 68 | return new Term(term); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/Vote.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import com.google.common.base.MoreObjects; 19 | import com.google.common.base.Objects; 20 | import java.io.Serializable; 21 | import javax.annotation.concurrent.Immutable; 22 | 23 | /** 24 | */ 25 | @Immutable 26 | public class Vote implements Serializable { 27 | 28 | private final String votedFor; 29 | 30 | public Vote(String votedFor) { 31 | this.votedFor = votedFor; 32 | } 33 | 34 | public static Builder newBuilder() { 35 | return new Builder(); 36 | } 37 | 38 | public String getVotedFor() { 39 | return votedFor; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | return o instanceof Vote && Objects.equal(votedFor, ((Vote) o).votedFor); 45 | 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return votedFor != null ? votedFor.hashCode() : 0; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return MoreObjects.toStringHelper(this) 56 | .add("votedFor", votedFor) 57 | .toString(); 58 | } 59 | 60 | public static class Builder { 61 | private String votedFor; 62 | 63 | public Builder setVotedFor(String votedFor) { 64 | this.votedFor = votedFor; 65 | return this; 66 | } 67 | 68 | public Vote build() { 69 | return new Vote(votedFor); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

Domain Model Objects

3 | *

This package group the domain model objects for Raft protocol implementation. These are POJOs which 4 | * are used to represent:

5 | *
    6 | *
  • The messages exchanged between raft instances part of the same replica set 7 | * through {@link org.robotninjas.barge.rpc.RaftClient},
  • 8 | *
  • The entries in the {@link org.robotninjas.barge.log.RaftLog} that persists the state of each replica.
  • 9 | *
10 | */ 11 | package org.robotninjas.barge.api; 12 | 13 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/log/GetEntriesResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.log; 18 | 19 | import com.google.common.base.MoreObjects; 20 | import com.google.common.collect.Lists; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import javax.annotation.Nonnull; 24 | import javax.annotation.concurrent.Immutable; 25 | import javax.annotation.concurrent.ThreadSafe; 26 | import org.robotninjas.barge.api.Entry; 27 | 28 | @Immutable 29 | @ThreadSafe 30 | public class GetEntriesResult { 31 | 32 | private final long prevEntryTerm; 33 | private final long prevEntryIndex; 34 | private final List entries; 35 | 36 | public GetEntriesResult(long prevEntryTerm, long prevEntryIndex, Iterable entries) { 37 | this.prevEntryTerm = prevEntryTerm; 38 | this.prevEntryIndex = prevEntryIndex; 39 | this.entries = Lists.newArrayList(entries); 40 | } 41 | 42 | public long lastLogTerm() { 43 | return prevEntryTerm; 44 | } 45 | 46 | public long lastLogIndex() { 47 | return prevEntryIndex; 48 | } 49 | 50 | @Nonnull 51 | public List entries() { 52 | return Collections.unmodifiableList(entries); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return MoreObjects.toStringHelper(getClass()) 58 | .add("prevEntryIndex", prevEntryIndex) 59 | .add("prevEntryTerm", prevEntryTerm) 60 | .add("numEntries", entries.size()) 61 | .toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/log/LogModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.log; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import com.google.inject.PrivateModule; 22 | import com.google.inject.Provides; 23 | import com.google.inject.Singleton; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.concurrent.Executor; 27 | import javax.annotation.Nonnull; 28 | import journal.io.api.Journal; 29 | import journal.io.api.JournalBuilder; 30 | import org.robotninjas.barge.StateMachine; 31 | 32 | public class LogModule extends PrivateModule { 33 | 34 | private final File logDirectory; 35 | private final StateMachine stateMachine; 36 | private final Executor executor; 37 | 38 | public LogModule(@Nonnull File logDirectory, @Nonnull StateMachine stateMachine, @Nonnull Executor executor) { 39 | this.logDirectory = checkNotNull(logDirectory); 40 | this.stateMachine = checkNotNull(stateMachine); 41 | this.executor = checkNotNull(executor); 42 | } 43 | 44 | @Override 45 | protected void configure() { 46 | 47 | bind(StateMachine.class).toInstance(stateMachine); 48 | bind(StateMachineProxy.class); 49 | bind(Executor.class) 50 | .annotatedWith(StateExecutor.class) 51 | .toInstance(executor); 52 | bind(RaftLog.class).asEagerSingleton(); 53 | expose(RaftLog.class); 54 | 55 | } 56 | 57 | @Nonnull 58 | @Provides 59 | @Singleton 60 | Journal getJournal() { 61 | 62 | try { 63 | 64 | final Journal journal = JournalBuilder.of(logDirectory).setPhysicalSync(true).open(); 65 | 66 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 67 | //noinspection EmptyCatchBlock 68 | try { 69 | journal.close(); 70 | } catch (IOException e) { 71 | //TODO log it 72 | } 73 | })); 74 | 75 | return journal; 76 | 77 | } catch (IOException e) { 78 | 79 | throw new RuntimeException(e); 80 | 81 | } 82 | 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/log/StateExecutor.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.log; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.ElementType.METHOD; 5 | import static java.lang.annotation.ElementType.PARAMETER; 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | import com.google.inject.BindingAnnotation; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.Target; 11 | 12 | @BindingAnnotation 13 | @Target({FIELD, PARAMETER, METHOD}) 14 | @Retention(RUNTIME) 15 | @interface StateExecutor { 16 | } 17 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/log/StateMachineProxy.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.log; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import com.google.common.collect.Queues; 6 | import com.google.inject.Inject; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.nio.ByteBuffer; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.Executor; 13 | import java.util.concurrent.LinkedBlockingQueue; 14 | import java.util.concurrent.atomic.AtomicBoolean; 15 | import javax.annotation.Nonnull; 16 | import javax.annotation.concurrent.ThreadSafe; 17 | import org.robotninjas.barge.StateMachine; 18 | 19 | @ThreadSafe 20 | class StateMachineProxy { 21 | 22 | private final int BATCH_SIZE = 10; 23 | 24 | private final Executor executor; 25 | private final StateMachine stateMachine; 26 | private final LinkedBlockingQueue operations; 27 | private final AtomicBoolean running; 28 | 29 | @Inject 30 | StateMachineProxy(@Nonnull @StateExecutor Executor executor, @Nonnull StateMachine stateMachine) { 31 | this.executor = checkNotNull(executor); 32 | this.stateMachine = checkNotNull(stateMachine); 33 | this.operations = Queues.newLinkedBlockingQueue(); 34 | this.running = new AtomicBoolean(false); 35 | } 36 | 37 | private CompletableFuture submit(Callable runnable) { 38 | return CompletableFuture.supplyAsync(() -> { 39 | try { 40 | return runnable.call(); 41 | } catch(Exception e) { 42 | throw new RuntimeException(e); 43 | } 44 | }, executor); 45 | } 46 | 47 | @Nonnull 48 | public CompletableFuture dispatchOperation(@Nonnull final ByteBuffer op) { 49 | checkNotNull(op); 50 | return submit(() -> stateMachine.applyOperation(op.asReadOnlyBuffer())); 51 | } 52 | 53 | @Nonnull 54 | public CompletableFuture takeSnapshot(@Nonnull final OutputStream out) throws IOException { 55 | checkNotNull(out); 56 | return submit((Callable) () -> { 57 | //stateMachine.takeSnapshot(out); 58 | return null; 59 | }); 60 | } 61 | 62 | @Nonnull 63 | public CompletableFuture installSnapshot() { 64 | CompletableFuture failed = new CompletableFuture(); 65 | failed.completeExceptionally(new IllegalStateException()); 66 | return failed; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/rpc/Client.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.rpc; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | import static java.util.function.Function.identity; 21 | 22 | import com.google.inject.Inject; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.Executor; 25 | import javax.annotation.Nonnull; 26 | import javax.annotation.concurrent.Immutable; 27 | import org.jetlang.fibers.Fiber; 28 | import org.robotninjas.barge.RaftExecutor; 29 | import org.robotninjas.barge.Replica; 30 | import org.robotninjas.barge.api.AppendEntries; 31 | import org.robotninjas.barge.api.AppendEntriesResponse; 32 | import org.robotninjas.barge.api.RequestVote; 33 | import org.robotninjas.barge.api.RequestVoteResponse; 34 | 35 | @Immutable 36 | public class Client { 37 | 38 | @Nonnull 39 | private final RaftClientProvider clientProvider; 40 | @Nonnull 41 | private final Executor executor; 42 | 43 | @Inject 44 | public Client(@Nonnull RaftClientProvider clientProvider, @Nonnull @RaftExecutor Fiber executor) { 45 | this.clientProvider = clientProvider; 46 | this.executor = executor; 47 | } 48 | 49 | @Nonnull 50 | public CompletableFuture requestVote(@Nonnull final Replica replica, @Nonnull final RequestVote request) { 51 | 52 | checkNotNull(replica); 53 | checkNotNull(request); 54 | 55 | return clientProvider.get(replica).requestVote(request).thenApplyAsync(identity(), executor); 56 | } 57 | 58 | @Nonnull 59 | public CompletableFuture appendEntries(@Nonnull final Replica replica, @Nonnull final AppendEntries request) { 60 | 61 | checkNotNull(replica); 62 | checkNotNull(request); 63 | 64 | return clientProvider.get(replica).appendEntries(request).thenApplyAsync(identity(), executor); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/rpc/RaftClient.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.rpc; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import org.robotninjas.barge.api.AppendEntries; 5 | import org.robotninjas.barge.api.AppendEntriesResponse; 6 | import org.robotninjas.barge.api.RequestVote; 7 | import org.robotninjas.barge.api.RequestVoteResponse; 8 | 9 | public interface RaftClient { 10 | 11 | CompletableFuture requestVote(RequestVote request); 12 | 13 | CompletableFuture appendEntries(AppendEntries request); 14 | } 15 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/rpc/RaftClientProvider.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.rpc; 2 | 3 | import org.robotninjas.barge.Replica; 4 | 5 | public interface RaftClientProvider { 6 | RaftClient get(Replica replica); 7 | } 8 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/AbstractListenersModule.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.binder.LinkedBindingBuilder; 5 | import com.google.inject.multibindings.Multibinder; 6 | 7 | 8 | /** 9 | * A container for making it possible to bind multiple instance of {@link org.robotninjas.barge.state.StateTransitionListener}. 10 | * 11 | *

This class uses Guice's {@link Multibinder} extension to make it possible to inject a collection of listeners 12 | * into the context. It is intended to be extended by Raft applicative level code to customize what implementations 13 | * of listeners are implemented:

14 | *
15 |  * public class MyListenersModule extends ListenersModule {
16 |  *
17 |  * }
18 |  * 
19 | * 20 | * @see Guice Multibindings on their wiki 21 | * 22 | * @see Stack overflow 23 | * question on the subject, along with another possible answer. 24 | */ 25 | public abstract class AbstractListenersModule extends AbstractModule { 26 | 27 | private Multibinder listenerBinder; 28 | private Multibinder protocolListenerBinder; 29 | 30 | @Override 31 | protected void configure() { 32 | listenerBinder = Multibinder.newSetBinder(binder(), StateTransitionListener.class); 33 | protocolListenerBinder = Multibinder.newSetBinder(binder(), RaftProtocolListener.class); 34 | configureListeners(); 35 | } 36 | 37 | protected abstract void configureListeners(); 38 | 39 | protected final LinkedBindingBuilder bindTransitionListener() { 40 | return listenerBinder.addBinding(); 41 | } 42 | 43 | protected final LinkedBindingBuilder bindProtocolListener() { 44 | return protocolListenerBinder.addBinding(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/DeadlineTimer.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import static com.google.common.base.Preconditions.checkState; 4 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 5 | 6 | import java.util.Optional; 7 | import javax.annotation.concurrent.NotThreadSafe; 8 | import org.jetlang.core.Disposable; 9 | import org.jetlang.core.Scheduler; 10 | 11 | @NotThreadSafe 12 | class DeadlineTimer { 13 | 14 | private final Scheduler scheduler; 15 | private final Runnable action; 16 | private final long timeout; 17 | private boolean started = false; 18 | private Optional future; 19 | 20 | DeadlineTimer(Scheduler scheduler, Runnable action, long timeout) { 21 | this.scheduler = scheduler; 22 | this.action = action; 23 | this.timeout = timeout; 24 | this.future = Optional.empty(); 25 | } 26 | 27 | public void start() { 28 | checkState(!started); 29 | started = true; 30 | reset(); 31 | } 32 | 33 | public void reset() { 34 | checkState(started); 35 | future.ifPresent(Disposable::dispose); 36 | future = Optional.of(scheduler.schedule(action, timeout, MILLISECONDS)); 37 | } 38 | 39 | public void cancel() { 40 | checkState(started); 41 | future.ifPresent(Disposable::dispose); 42 | } 43 | 44 | public static DeadlineTimer start(Scheduler scheduler, Runnable action, long timeout) { 45 | DeadlineTimer t = new DeadlineTimer(scheduler, action, timeout); 46 | t.start(); 47 | return t; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/DefaultStateFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.state; 17 | 18 | import javax.annotation.Nonnegative; 19 | import javax.inject.Inject; 20 | import org.jetlang.fibers.Fiber; 21 | import org.robotninjas.barge.RaftExecutor; 22 | import org.robotninjas.barge.log.RaftLog; 23 | import org.robotninjas.barge.rpc.Client; 24 | 25 | class DefaultStateFactory implements StateFactory { 26 | 27 | 28 | private final RaftLog log; 29 | private final Fiber scheduler; 30 | private final long timeout; 31 | private final ReplicaManagerFactory replicaManagerFactory; 32 | private final Client client; 33 | 34 | @Inject 35 | public DefaultStateFactory(RaftLog log, @RaftExecutor Fiber scheduler, 36 | @ElectionTimeout @Nonnegative long timeout, ReplicaManagerFactory replicaManagerFactory, 37 | Client client) { 38 | this.log = log; 39 | this.scheduler = scheduler; 40 | this.timeout = timeout; 41 | this.replicaManagerFactory = replicaManagerFactory; 42 | this.client = client; 43 | } 44 | 45 | @Override 46 | public State makeState(RaftStateContext.StateType state) { 47 | switch (state) { 48 | case START: 49 | return new Start(log); 50 | case FOLLOWER: 51 | return new Follower(log, scheduler, timeout); 52 | case LEADER: 53 | return new Leader(log, scheduler, timeout, replicaManagerFactory); 54 | case CANDIDATE: 55 | return new Candidate(log, scheduler, timeout, client); 56 | case STOPPED: 57 | return new Stopped(log); 58 | default: 59 | throw new IllegalStateException("the impossible happpened, unknown state type " + state); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/ElectionTimeout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import static java.lang.annotation.ElementType.FIELD; 20 | import static java.lang.annotation.ElementType.METHOD; 21 | import static java.lang.annotation.ElementType.PARAMETER; 22 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 23 | 24 | import com.google.inject.BindingAnnotation; 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.Target; 27 | 28 | @BindingAnnotation 29 | @Target({FIELD, PARAMETER, METHOD}) 30 | @Retention(RUNTIME) 31 | @interface ElectionTimeout { 32 | } 33 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/Follower.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import static com.google.common.base.Preconditions.checkArgument; 20 | import static com.google.common.base.Preconditions.checkNotNull; 21 | import static org.robotninjas.barge.state.Raft.StateType.CANDIDATE; 22 | import static org.robotninjas.barge.state.Raft.StateType.FOLLOWER; 23 | 24 | import com.google.inject.Inject; 25 | import javax.annotation.Nonnegative; 26 | import javax.annotation.Nonnull; 27 | import javax.annotation.concurrent.NotThreadSafe; 28 | import org.jetlang.fibers.Fiber; 29 | import org.robotninjas.barge.RaftExecutor; 30 | import org.robotninjas.barge.log.RaftLog; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | @NotThreadSafe 35 | class Follower extends BaseState { 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(Follower.class); 38 | 39 | private final Fiber scheduler; 40 | private final long timeout; 41 | private DeadlineTimer timeoutTask; 42 | 43 | @Inject 44 | Follower(RaftLog log, @RaftExecutor Fiber scheduler, @ElectionTimeout @Nonnegative long timeout) { 45 | 46 | super(FOLLOWER, log); 47 | 48 | this.scheduler = checkNotNull(scheduler); 49 | checkArgument(timeout >= 0); 50 | this.timeout = timeout; 51 | 52 | } 53 | 54 | @Override 55 | public void init(@Nonnull final RaftStateContext ctx) { 56 | timeoutTask = DeadlineTimer.start(scheduler, () -> { 57 | LOGGER.debug("DeadlineTimer expired, starting election"); 58 | ctx.setState(Follower.this, CANDIDATE); 59 | }, timeout * 2); 60 | } 61 | 62 | @Override 63 | public void destroy(RaftStateContext ctx) { 64 | timeoutTask.cancel(); 65 | } 66 | 67 | 68 | protected void resetTimer() { 69 | timeoutTask.reset(); 70 | } 71 | 72 | @Override 73 | public void doStop(RaftStateContext ctx) { 74 | timeoutTask.cancel(); 75 | super.doStop(ctx); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/LogListener.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import javax.annotation.Nonnull; 4 | import javax.annotation.Nullable; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | 9 | /** 10 | */ 11 | public class LogListener implements StateTransitionListener { 12 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftStateContext.class); 13 | 14 | @Override 15 | public void changeState(@Nonnull Raft context, @Nullable Raft.StateType from, @Nonnull Raft.StateType to) { 16 | LOGGER.info("LogListener: old state: {}, new state: {}", from, to); 17 | } 18 | 19 | @Override 20 | public void invalidTransition(@Nonnull Raft context, @Nonnull Raft.StateType actual, @Nullable Raft.StateType expected) { 21 | LOGGER.warn("LogListener: State transition from incorrect previous state. Expected {}, was {}", actual, expected); 22 | } 23 | 24 | @Override 25 | public void stop(@Nonnull Raft raft) { 26 | LOGGER.info("Stopping {}", raft); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/MajorityCollector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import java.util.List; 22 | import java.util.concurrent.CompletableFuture; 23 | import java.util.concurrent.locks.ReentrantLock; 24 | import java.util.function.BiFunction; 25 | import java.util.function.Predicate; 26 | import javax.annotation.Nonnegative; 27 | import javax.annotation.Nonnull; 28 | import javax.annotation.concurrent.GuardedBy; 29 | import javax.annotation.concurrent.ThreadSafe; 30 | 31 | 32 | @ThreadSafe 33 | class MajorityCollector implements BiFunction { 34 | 35 | private final ReentrantLock lock = new ReentrantLock(); 36 | private final Predicate isSuccess; 37 | private final CompletableFuture future; 38 | private final int totalNum; 39 | @GuardedBy("lock") 40 | private int numSuccess = 0; 41 | @GuardedBy("lock") 42 | private int numFailed = 0; 43 | 44 | private MajorityCollector(@Nonnegative int totalNum, @Nonnull Predicate isSuccess, CompletableFuture future) { 45 | this.totalNum = totalNum; 46 | this.isSuccess = isSuccess; 47 | this.future = future; 48 | } 49 | 50 | @Nonnull 51 | public static CompletableFuture majorityResponse(@Nonnull List> responses, @Nonnull Predicate isSuccess) { 52 | CompletableFuture future = new CompletableFuture<>(); 53 | MajorityCollector collector = new MajorityCollector<>(responses.size(), isSuccess, future); 54 | for (CompletableFuture response : responses) { 55 | response.handle(collector); 56 | } 57 | if (responses.isEmpty()) { 58 | collector.checkComplete(); 59 | } 60 | return future; 61 | } 62 | 63 | private void checkComplete() { 64 | if (!future.isDone()) { 65 | final double half = totalNum / 2.0; 66 | if (numSuccess > half) { 67 | future.complete(true); 68 | } else if (numFailed >= half) { 69 | future.complete(false); 70 | } 71 | } 72 | } 73 | 74 | @Override 75 | public Boolean apply(T result, Throwable t) { 76 | lock.lock(); 77 | try { 78 | if (null != t) { 79 | numFailed++; 80 | } else { 81 | checkNotNull(result); 82 | if (isSuccess.test(result)) { 83 | numSuccess++; 84 | } else { 85 | numFailed++; 86 | } 87 | } 88 | checkComplete(); 89 | } finally { 90 | lock.unlock(); 91 | } 92 | return null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/Raft.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import javax.annotation.Nonnull; 5 | import org.robotninjas.barge.RaftException; 6 | import org.robotninjas.barge.api.AppendEntries; 7 | import org.robotninjas.barge.api.AppendEntriesResponse; 8 | import org.robotninjas.barge.api.RequestVote; 9 | import org.robotninjas.barge.api.RequestVoteResponse; 10 | 11 | 12 | /** 13 | * Main interface to a Raft protocol instance. 14 | */ 15 | public interface Raft { 16 | 17 | public enum StateType { 18 | START, FOLLOWER, CANDIDATE, LEADER, STOPPED 19 | } 20 | 21 | void addRaftProtocolListener(RaftProtocolListener protocolListener); 22 | 23 | CompletableFuture init(); 24 | 25 | @Nonnull RequestVoteResponse requestVote(@Nonnull RequestVote request); 26 | 27 | @Nonnull AppendEntriesResponse appendEntries(@Nonnull AppendEntries request); 28 | 29 | @Nonnull 30 | CompletableFuture commitOperation(@Nonnull byte[] op) throws RaftException; 31 | 32 | void addTransitionListener(@Nonnull StateTransitionListener transitionListener); 33 | 34 | @Nonnull StateType type(); 35 | 36 | void stop(); 37 | } 38 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/RaftPredicates.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import com.google.common.annotations.VisibleForTesting; 22 | import java.util.function.Predicate; 23 | import javax.annotation.Nonnull; 24 | import javax.annotation.Nullable; 25 | import javax.annotation.concurrent.Immutable; 26 | import org.robotninjas.barge.api.AppendEntriesResponse; 27 | import org.robotninjas.barge.api.RequestVoteResponse; 28 | 29 | @Immutable 30 | class RaftPredicates { 31 | 32 | @Nonnull 33 | static Predicate appendSuccessul() { 34 | return AppendSuccessPredicate.Success; 35 | } 36 | 37 | @Nonnull 38 | static Predicate voteGranted() { 39 | return VoteGrantedPredicate.VoteGranted; 40 | } 41 | 42 | /** 43 | * Predicate returning true if the {@link AppendEntriesResponse} returns success. 44 | */ 45 | @Immutable 46 | @VisibleForTesting 47 | enum AppendSuccessPredicate implements Predicate { 48 | 49 | Success; 50 | 51 | @Override 52 | public boolean test(@Nullable AppendEntriesResponse input) { 53 | checkNotNull(input); 54 | return input.getSuccess(); 55 | } 56 | 57 | } 58 | 59 | /** 60 | * Predicate returning true if the {@link RequestVoteResponse} returns voteGranted. 61 | */ 62 | @Immutable 63 | @VisibleForTesting 64 | enum VoteGrantedPredicate implements Predicate { 65 | 66 | VoteGranted; 67 | 68 | @Override 69 | public boolean test(@Nullable RequestVoteResponse input) { 70 | checkNotNull(input); 71 | return input.getVoteGranted(); 72 | } 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/RaftProtocolListener.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import javax.annotation.Nonnull; 4 | import org.robotninjas.barge.api.AppendEntries; 5 | import org.robotninjas.barge.api.RequestVote; 6 | 7 | /** 8 | * A listener which is notified of protocol messages received by a {@link org.robotninjas.barge.state.RaftStateContext} 9 | * instance. 10 | */ 11 | public interface RaftProtocolListener { 12 | 13 | /** 14 | * Called when {@code raft} instance is initialized. 15 | *

16 | * This method is called after instance has been initialized. 17 | *

18 | * @param raft the raft instance. 19 | */ 20 | void init(Raft raft); 21 | 22 | /** 23 | * Called after append entries has been received and processed by {@code raft} instance. 24 | * 25 | * @param raft the raft instance receiving RPC. 26 | * @param entries the appended entries. 27 | */ 28 | void appendEntries(@Nonnull Raft raft, @Nonnull AppendEntries entries); 29 | 30 | /** 31 | * Called after request vote has been received and processed by {@code raft} instance. 32 | * 33 | * @param raft the raft instance receiving RPC. 34 | * @param vote the vote request. 35 | */ 36 | void requestVote(@Nonnull Raft raft, @Nonnull RequestVote vote); 37 | 38 | /** 39 | * Called after commit request has been received and processed by {@code raft} instance. 40 | * @param raft the raft instance receiving RPC. 41 | * @param operation bytes to be committed to underlying state machine. Maybe empty. 42 | */ 43 | void commit(@Nonnull Raft raft, @Nonnull byte[] operation); 44 | } 45 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/ReplicaManagerFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import javax.annotation.Nonnull; 20 | import org.robotninjas.barge.Replica; 21 | 22 | interface ReplicaManagerFactory { 23 | 24 | @Nonnull 25 | ReplicaManager create(@Nonnull Replica remote); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/ReplicaState.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.atomic.AtomicBoolean; 5 | import org.robotninjas.barge.api.AppendEntriesResponse; 6 | 7 | public class ReplicaState { 8 | 9 | private final AtomicBoolean running = new AtomicBoolean(false); 10 | private final AtomicBoolean ready = new AtomicBoolean(false); 11 | private volatile long nextIndex = 0; 12 | private volatile long matchIndex = 0; 13 | 14 | void init() { 15 | dispatch(); 16 | } 17 | 18 | CompletableFuture sendUpdate() { 19 | return null; 20 | } 21 | 22 | void requestUpdate() { 23 | ready.set(true); 24 | } 25 | 26 | void dispatch() { 27 | 28 | if (!running.get() && ready.get()) { 29 | 30 | if (running.compareAndSet(false, true)) { 31 | 32 | ready.set(false); 33 | 34 | sendUpdate().handle((result, t) -> { 35 | if (null != t) { 36 | running.set(false); 37 | } 38 | return null; 39 | }); 40 | 41 | } 42 | 43 | } 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/Start.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import static org.robotninjas.barge.state.Raft.StateType.FOLLOWER; 4 | import static org.robotninjas.barge.state.Raft.StateType.START; 5 | 6 | import com.google.inject.Inject; 7 | import java.util.concurrent.CompletableFuture; 8 | import javax.annotation.Nonnull; 9 | import org.robotninjas.barge.RaftException; 10 | import org.robotninjas.barge.api.AppendEntries; 11 | import org.robotninjas.barge.api.AppendEntriesResponse; 12 | import org.robotninjas.barge.api.RequestVote; 13 | import org.robotninjas.barge.api.RequestVoteResponse; 14 | import org.robotninjas.barge.log.RaftLog; 15 | import org.slf4j.MDC; 16 | 17 | class Start extends BaseState { 18 | 19 | @Inject 20 | public Start(RaftLog log) { 21 | super(START, log); 22 | } 23 | 24 | @Override 25 | public void init(@Nonnull RaftStateContext ctx) { 26 | RaftLog log = getLog(); 27 | 28 | MDC.put("state", Raft.StateType.START.name()); 29 | MDC.put("term", Long.toString(log.currentTerm())); 30 | MDC.put("self", log.self().toString()); 31 | log.load(); 32 | ctx.setState(this, FOLLOWER); 33 | } 34 | 35 | @Nonnull 36 | @Override 37 | public RequestVoteResponse requestVote(@Nonnull RaftStateContext ctx, @Nonnull RequestVote request) { 38 | throw new RuntimeException("Service unavailable"); 39 | } 40 | 41 | @Nonnull 42 | @Override 43 | public AppendEntriesResponse appendEntries(@Nonnull RaftStateContext ctx, @Nonnull AppendEntries request) { 44 | throw new RuntimeException("Service unavailable"); 45 | } 46 | 47 | @Nonnull 48 | @Override 49 | public CompletableFuture commitOperation(@Nonnull RaftStateContext ctx, @Nonnull byte[] operation) throws RaftException { 50 | throw new RaftException("Service has not started yet"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/State.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import static org.robotninjas.barge.state.RaftStateContext.StateType; 20 | 21 | import java.util.concurrent.CompletableFuture; 22 | import javax.annotation.Nonnull; 23 | import org.robotninjas.barge.RaftException; 24 | import org.robotninjas.barge.api.AppendEntries; 25 | import org.robotninjas.barge.api.AppendEntriesResponse; 26 | import org.robotninjas.barge.api.RequestVote; 27 | import org.robotninjas.barge.api.RequestVoteResponse; 28 | 29 | interface State { 30 | 31 | void init(@Nonnull RaftStateContext ctx); 32 | 33 | void destroy(@Nonnull RaftStateContext ctx); 34 | 35 | @Nonnull 36 | RequestVoteResponse requestVote(@Nonnull RaftStateContext ctx, @Nonnull RequestVote request); 37 | 38 | @Nonnull 39 | AppendEntriesResponse appendEntries(@Nonnull RaftStateContext ctx, @Nonnull AppendEntries request); 40 | 41 | @Nonnull 42 | CompletableFuture commitOperation(@Nonnull RaftStateContext ctx, @Nonnull byte[] operation) throws RaftException; 43 | 44 | void doStop(RaftStateContext ctx); 45 | 46 | StateType type(); 47 | } 48 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/StateFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | interface StateFactory { 20 | 21 | State makeState(RaftStateContext.StateType state); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/StateModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.state; 18 | 19 | import com.google.inject.PrivateModule; 20 | import com.google.inject.assistedinject.FactoryModuleBuilder; 21 | 22 | public class StateModule extends PrivateModule { 23 | 24 | private final long electionTimeout; 25 | 26 | public StateModule(long electionTimeout) { 27 | this.electionTimeout = electionTimeout; 28 | } 29 | 30 | @Override 31 | protected void configure() { 32 | 33 | bind(StateFactory.class).to(DefaultStateFactory.class); 34 | 35 | install(new FactoryModuleBuilder() 36 | .build(ReplicaManagerFactory.class)); 37 | 38 | bind(Long.class) 39 | .annotatedWith(ElectionTimeout.class) 40 | .toInstance(electionTimeout); 41 | 42 | bind(Raft.class) 43 | .to(RaftStateContext.class) 44 | .asEagerSingleton(); 45 | 46 | expose(Raft.class); 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/StateTransitionListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.state; 17 | 18 | import static org.robotninjas.barge.state.RaftStateContext.StateType; 19 | 20 | import javax.annotation.Nonnull; 21 | import javax.annotation.Nullable; 22 | 23 | 24 | /** 25 | * An interface for being notified of state transitions within a given Raft instance. 26 | */ 27 | public interface StateTransitionListener { 28 | 29 | /** 30 | * Called when an instance has just transitioned between states. 31 | * 32 | * @param context the context originating the state transition. Useful when several replica instances might live within 33 | * the same JVM space and notifies a single listener. 34 | * @param from previous state of replica. 35 | * @param to current state of replica. 36 | */ 37 | void changeState(@Nonnull Raft context, @Nullable StateType from, @Nonnull StateType to); 38 | 39 | /** 40 | * Called when a transition is requested from an invalid state. 41 | * 42 | * @param context the context originating the state transition. Useful when several replica instances might live within 43 | * the same JVM space and notifies a single listener. 44 | * @param actual the actual state registered in context. 45 | * @param expected the expected state as requested by transition. 46 | */ 47 | void invalidTransition(@Nonnull Raft context, @Nonnull StateType actual, @Nullable StateType expected); 48 | 49 | /** 50 | * Called when raft instance is stopped. 51 | * 52 | * @param raft the raft instance which is stopping. 53 | */ 54 | void stop(@Nonnull Raft raft); 55 | } 56 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/state/Stopped.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.state; 2 | 3 | import static org.robotninjas.barge.state.Raft.StateType.STOPPED; 4 | 5 | import com.google.inject.Inject; 6 | import java.util.concurrent.CompletableFuture; 7 | import javax.annotation.Nonnull; 8 | import org.robotninjas.barge.RaftException; 9 | import org.robotninjas.barge.api.AppendEntries; 10 | import org.robotninjas.barge.api.AppendEntriesResponse; 11 | import org.robotninjas.barge.api.RequestVote; 12 | import org.robotninjas.barge.api.RequestVoteResponse; 13 | import org.robotninjas.barge.log.RaftLog; 14 | 15 | class Stopped extends BaseState { 16 | 17 | @Inject 18 | public Stopped(RaftLog log) { 19 | super(STOPPED, log); 20 | } 21 | 22 | @Override 23 | public void init(RaftStateContext ctx) { 24 | } 25 | 26 | @Nonnull 27 | @Override 28 | public RequestVoteResponse requestVote(@Nonnull RaftStateContext ctx, @Nonnull RequestVote request) { 29 | throw new RuntimeException("Service is stopped"); 30 | } 31 | 32 | @Nonnull 33 | @Override 34 | public AppendEntriesResponse appendEntries(@Nonnull RaftStateContext ctx, @Nonnull AppendEntries request) { 35 | throw new RuntimeException("Service is stopped"); 36 | } 37 | 38 | @Nonnull 39 | @Override 40 | public CompletableFuture commitOperation(@Nonnull RaftStateContext ctx, @Nonnull byte[] operation) throws RaftException { 41 | throw new RaftException("Service is stopped"); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/utils/Files.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.utils; 2 | 3 | import java.io.File; 4 | 5 | 6 | /** 7 | */ 8 | public class Files { 9 | @SuppressWarnings({ "ConstantConditions", "ResultOfMethodCallIgnored" }) 10 | public static void delete(File directory) { 11 | 12 | for (File file : directory.listFiles()) { 13 | 14 | if (file.isFile()) { 15 | file.delete(); 16 | } else { 17 | delete(file); 18 | } 19 | } 20 | 21 | directory.delete(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /barge-core/src/main/java/org/robotninjas/barge/utils/Prober.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.utils; 17 | 18 | import java.util.concurrent.Callable; 19 | 20 | /** 21 | * Repeatedly probe for some condition to become true. 22 | */ 23 | public class Prober { 24 | 25 | private Callable condition; 26 | 27 | public Prober(Callable condition) { 28 | this.condition = condition; 29 | } 30 | 31 | /** 32 | * Start probing, waiting for {@code condition} to become true. 33 | *

34 | *

This method is blocking: It returns when condition becomes true or throws an exception if timeout is reached.

35 | * 36 | * @param timeoutInMs wait time in milliseconds 37 | */ 38 | public void probe(long timeoutInMs) { 39 | long start = System.nanoTime(); 40 | 41 | try { 42 | while (!condition.call() && (elapsedMs(start) < timeoutInMs)) { 43 | Thread.yield(); 44 | } 45 | 46 | if (!condition.call()) { 47 | throw new IllegalStateException("probe condition not true after " + timeoutInMs); 48 | } 49 | } catch (Exception e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | 54 | private long elapsedMs(long start) { 55 | return (System.nanoTime() - start) / 1000000; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /barge-core/src/test/java/org/robotninjas/barge/ClusterConfigStub.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | 5 | import com.google.common.base.Objects; 6 | import com.google.common.collect.Lists; 7 | 8 | public class ClusterConfigStub implements ClusterConfig { 9 | 10 | private final Replica local = new ReplicaStub("local"); 11 | private final Iterable remote; 12 | 13 | private ClusterConfigStub(Iterable replicas) { 14 | remote = Lists.newArrayList(replicas); 15 | } 16 | 17 | public static ClusterConfigStub getStub(String... ids) { 18 | return new ClusterConfigStub( 19 | Lists.newArrayList(ids).stream() 20 | .map(ReplicaStub::new) 21 | .collect(toList()) 22 | ); 23 | } 24 | 25 | @Override 26 | public Replica local() { 27 | return local; 28 | } 29 | 30 | @Override 31 | public Iterable remote() { 32 | return remote; 33 | } 34 | 35 | @Override 36 | public Replica getReplica(String info) { 37 | return new ReplicaStub(info); 38 | } 39 | 40 | private static class ReplicaStub implements Replica { 41 | 42 | private final String info; 43 | 44 | private ReplicaStub(String info) { 45 | this.info = info; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hashCode(info); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | 56 | if (o == this) { 57 | return true; 58 | } 59 | 60 | if (!(o instanceof ReplicaStub)) { 61 | return false; 62 | } 63 | 64 | ReplicaStub stub = (ReplicaStub) o; 65 | return Objects.equal(stub.info, info); 66 | 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return info; 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /barge-core/src/test/java/org/robotninjas/barge/api/JournalEntryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.api; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.junit.Test; 21 | import org.junit.experimental.theories.DataPoints; 22 | import org.junit.experimental.theories.Theories; 23 | import org.junit.experimental.theories.Theory; 24 | import org.junit.runner.RunWith; 25 | 26 | /** 27 | */ 28 | @RunWith(Theories.class) 29 | public class JournalEntryTest { 30 | 31 | @DataPoints 32 | public static Object[] entries() { 33 | return new Object[]{ 34 | new Commit(42), 35 | new Append(12, new Entry("foo".getBytes(), 2)), 36 | new Term(3), 37 | new Snapshot(12, 14, "bar"), 38 | new Vote("baz") 39 | }; 40 | } 41 | 42 | @Theory 43 | @Test 44 | public void canWriteAndReadACommitEntry(Object data) throws Exception { 45 | JournalEntry entry = new JournalEntry(data); 46 | 47 | assertThat(JournalEntry.parseFrom(entry.toByteArray())).isEqualTo(entry); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /barge-core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /barge-jax-rs/barge.conf: -------------------------------------------------------------------------------- 1 | 0=http://localhost:56789/ 2 | 1=http://localhost:56790/ 3 | 2=http://localhost:56791/ 4 | -------------------------------------------------------------------------------- /barge-jax-rs/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -vx 2 | # send init to all registered members of the cluster 3 | 4 | CONF=barge.conf 5 | 6 | if [ $# -gt 0 ]; then 7 | echo "using $1 as configuration file..." 8 | CONF=$1 9 | fi 10 | 11 | if [ ! -r $CONF ]; then 12 | echo "configuration $CONF does not exist or not readable, giving up!" 13 | exit 1 14 | fi 15 | 16 | cat $CONF | tr "=" " " | while read index uri; do 17 | curl -X POST ${uri}raft/init 18 | done 19 | -------------------------------------------------------------------------------- /barge-jax-rs/pack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # self-extractible script 3 | MYSELF=`which "$0" 2>/dev/null` 4 | [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" 5 | java=java 6 | if test -n "$JAVA_HOME"; then 7 | java="$JAVA_HOME/bin/java" 8 | fi 9 | exec "$java" $java_args -jar $MYSELF "$@" 10 | exit 1 11 | 12 | -------------------------------------------------------------------------------- /barge-jax-rs/send.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -vx 2 | 3 | function usage () { 4 | cat < 6 | Send some data to commmit to given server, in binary form 7 | EOF 8 | } 9 | 10 | if [ $# -ne 2 ]; then 11 | usage 12 | exit 1 13 | fi 14 | 15 | URI=$1 16 | 17 | while expr $URI : '.*/$' 2> /dev/null; do 18 | URI=$(expr $URI : '\(.*\)/') 19 | done 20 | 21 | curl -X POST -H "Content-type:application/octet-stream" -d "$2" $URI/raft/commit 22 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/BargeResource.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import javax.inject.Inject; 19 | import javax.ws.rs.Consumes; 20 | import javax.ws.rs.GET; 21 | import javax.ws.rs.POST; 22 | import javax.ws.rs.Path; 23 | import javax.ws.rs.Produces; 24 | import javax.ws.rs.core.MediaType; 25 | import javax.ws.rs.core.Response; 26 | import org.robotninjas.barge.ClusterConfig; 27 | import org.robotninjas.barge.NotLeaderException; 28 | import org.robotninjas.barge.api.AppendEntries; 29 | import org.robotninjas.barge.api.AppendEntriesResponse; 30 | import org.robotninjas.barge.api.RequestVote; 31 | import org.robotninjas.barge.api.RequestVoteResponse; 32 | import org.robotninjas.barge.state.Raft; 33 | 34 | 35 | /** 36 | * Exposes a Raft instance as a REST endpoint. 37 | *

38 | * This is the server part of a Barge REST instance, exposing RPCs as endpoints. All messages are serialized through 39 | * JSON. 40 | *

41 | */ 42 | @Path("/") 43 | @Produces(MediaType.APPLICATION_JSON) 44 | @Consumes(MediaType.APPLICATION_JSON) 45 | @SuppressWarnings("UnusedDeclaration") 46 | public class BargeResource { 47 | 48 | private final Raft raft; 49 | private final ClusterConfig clusterConfig; 50 | 51 | @Inject 52 | public BargeResource(Raft raft, ClusterConfig clusterConfig) { 53 | this.raft = raft; 54 | this.clusterConfig = clusterConfig; 55 | } 56 | 57 | @Path("/init") 58 | @POST 59 | public Raft.StateType init() { 60 | 61 | try { 62 | return raft.init().get(); 63 | } catch (Exception e) { 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | 68 | @Path("/config") 69 | @GET 70 | public ClusterConfig config(){ 71 | return clusterConfig; 72 | } 73 | 74 | @Path("/state") 75 | @GET 76 | public Raft.StateType state() { 77 | return raft.type(); 78 | } 79 | 80 | @Path("/vote") 81 | @POST 82 | public RequestVoteResponse requestVote(RequestVote vote) { 83 | return raft.requestVote(vote); 84 | } 85 | 86 | @Path("/entries") 87 | @POST 88 | public AppendEntriesResponse appendEntries(AppendEntries appendEntries) { 89 | return raft.appendEntries(appendEntries); 90 | } 91 | 92 | @Path("/commit") 93 | @POST 94 | @Consumes(MediaType.APPLICATION_OCTET_STREAM) 95 | public Response commit(byte[] operation) { 96 | 97 | try { 98 | raft.commitOperation(operation).get(); 99 | 100 | return Response.noContent().build(); 101 | } catch (NotLeaderException e) { 102 | return Response.status(Response.Status.FOUND).location(((HttpReplica) e.getLeader()).getUri()).build(); 103 | } catch (Exception e) { 104 | throw new RuntimeException(e); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/HttpReplica.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import com.google.common.base.Objects; 19 | import java.net.URI; 20 | import org.robotninjas.barge.Replica; 21 | 22 | /** 23 | */ 24 | public class HttpReplica implements Replica { 25 | 26 | private final URI uri; 27 | 28 | public HttpReplica(URI uri) { 29 | this.uri = uri; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (!(o instanceof HttpReplica)) return false; 36 | 37 | HttpReplica that = (HttpReplica) o; 38 | 39 | return Objects.equal(uri, that.uri); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return uri.hashCode(); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return uri.toString(); 50 | } 51 | 52 | boolean match(URI uri) { 53 | return this.uri.equals(uri); 54 | } 55 | 56 | public URI getUri() { 57 | return uri; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/JaxRsRaftModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import com.google.inject.PrivateModule; 19 | import java.io.File; 20 | import java.util.List; 21 | import org.robotninjas.barge.ClusterConfig; 22 | import org.robotninjas.barge.RaftCoreModule; 23 | import org.robotninjas.barge.StateMachine; 24 | import org.robotninjas.barge.jaxrs.client.HttpRaftClientProvider; 25 | import org.robotninjas.barge.jaxrs.ws.WsEventListenersModule; 26 | import org.robotninjas.barge.rpc.RaftClientProvider; 27 | import org.robotninjas.barge.state.Raft; 28 | import org.robotninjas.barge.state.RaftProtocolListener; 29 | import org.robotninjas.barge.state.StateTransitionListener; 30 | 31 | 32 | /** 33 | */ 34 | public class JaxRsRaftModule extends PrivateModule { 35 | 36 | private final ClusterConfig clusterConfig; 37 | private final File logDir; 38 | private final StateMachine stateMachine; 39 | private final long timeoutInMs; 40 | private final List transitionListeners; 41 | private final List protocolListeners; 42 | 43 | public JaxRsRaftModule(ClusterConfig clusterConfig, File logDir, StateMachine stateMachine, long timeoutInMs, 44 | List transitionListeners, List protocolListeners) { 45 | this.clusterConfig = clusterConfig; 46 | this.logDir = logDir; 47 | this.stateMachine = stateMachine; 48 | this.timeoutInMs = timeoutInMs; 49 | this.transitionListeners = transitionListeners; 50 | this.protocolListeners = protocolListeners; 51 | } 52 | 53 | @Override 54 | protected void configure() { 55 | 56 | install(new WsEventListenersModule(transitionListeners, protocolListeners)); 57 | 58 | install(RaftCoreModule.builder() 59 | .withTimeout(timeoutInMs) 60 | .withConfig(clusterConfig) 61 | .withLogDir(logDir) 62 | .withStateMachine(stateMachine) 63 | .build()); 64 | 65 | bind(RaftClientProvider.class).to(HttpRaftClientProvider.class).asEagerSingleton(); 66 | 67 | expose(RaftClientProvider.class); 68 | expose(Raft.class); 69 | expose(ClusterConfig.class); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/RaftHttpException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | /** 19 | */ 20 | public class RaftHttpException extends RuntimeException { 21 | 22 | public RaftHttpException(String message, Throwable throwable) { 23 | super(message, throwable); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/RaftJdkServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import com.sun.net.httpserver.HttpServer; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.URI; 22 | import javax.ws.rs.core.UriBuilder; 23 | import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory; 24 | 25 | 26 | /** 27 | * A dedicated server for an instance of Raft using JDK's embedded HTTP server. 28 | */ 29 | public class RaftJdkServer implements RaftServer { 30 | 31 | private final int serverIndex; 32 | private final URI[] uris; 33 | private final RaftApplication application; 34 | 35 | private HttpServer httpServer; 36 | 37 | public RaftJdkServer(int serverIndex, URI[] uris, File logDir) { 38 | this.serverIndex = serverIndex; 39 | this.uris = uris; 40 | this.application = new RaftApplication(serverIndex, uris, logDir); 41 | } 42 | 43 | 44 | public RaftJdkServer start(int unusedPort) { 45 | this.httpServer = JdkHttpServerFactory.createHttpServer(UriBuilder.fromUri(uris[serverIndex]).path("raft").build(), 46 | application.makeResourceConfig()); 47 | 48 | return this; 49 | } 50 | 51 | public void stop() { 52 | application.stop(); 53 | httpServer.stop(0); 54 | } 55 | 56 | @Override 57 | public void clean() { 58 | 59 | try { 60 | application.clean(); 61 | } catch (IOException e) { 62 | throw new RuntimeException(e); 63 | } 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/RaftServer.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs; 2 | 3 | /** 4 | */ 5 | public interface RaftServer> { 6 | 7 | S start(int port); 8 | 9 | void stop(); 10 | 11 | /** 12 | * Clean up persistent data stored by the server. 13 | */ 14 | void clean(); 15 | } 16 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/client/BargeJaxRsClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs.client; 17 | 18 | import com.google.common.annotations.VisibleForTesting; 19 | import java.net.URI; 20 | import java.util.concurrent.CompletableFuture; 21 | import javax.ws.rs.client.Client; 22 | import javax.ws.rs.client.ClientBuilder; 23 | import javax.ws.rs.client.Entity; 24 | import javax.ws.rs.client.InvocationCallback; 25 | import javax.ws.rs.core.MediaType; 26 | import javax.ws.rs.core.Response; 27 | import org.robotninjas.barge.api.AppendEntries; 28 | import org.robotninjas.barge.api.AppendEntriesResponse; 29 | import org.robotninjas.barge.api.RequestVote; 30 | import org.robotninjas.barge.api.RequestVoteResponse; 31 | import org.robotninjas.barge.jaxrs.Jackson; 32 | import org.robotninjas.barge.rpc.RaftClient; 33 | 34 | /** 35 | */ 36 | public class BargeJaxRsClient implements RaftClient { 37 | 38 | private final Client client; 39 | private final URI baseUri; 40 | 41 | @SuppressWarnings("UnusedDeclaration") 42 | public BargeJaxRsClient(URI baseUri) { 43 | this(baseUri, makeClient()); 44 | } 45 | 46 | @VisibleForTesting 47 | public BargeJaxRsClient(URI baseUri, Client client) { 48 | this.baseUri = baseUri; 49 | this.client = client; 50 | } 51 | 52 | @Override 53 | public CompletableFuture requestVote(RequestVote request) { 54 | final CompletableFuture result = new CompletableFuture<>(); 55 | 56 | client.target(baseUri).path("/raft/vote") 57 | .request().async() 58 | .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE), new InvocationCallback() { 59 | @Override 60 | public void completed(Response response) { 61 | result.complete(response.readEntity(RequestVoteResponse.class)); 62 | } 63 | 64 | @Override 65 | public void failed(Throwable throwable) { 66 | result.completeExceptionally(throwable); 67 | } 68 | }); 69 | 70 | return result; 71 | } 72 | 73 | @Override 74 | public CompletableFuture appendEntries(AppendEntries request) { 75 | final CompletableFuture result = new CompletableFuture<>(); 76 | 77 | client.target(baseUri).path("/raft/entries") 78 | .request().async() 79 | .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE), new InvocationCallback() { 80 | @Override 81 | public void completed(Response response) { 82 | result.complete(response.readEntity(AppendEntriesResponse.class)); 83 | } 84 | 85 | @Override 86 | public void failed(Throwable throwable) { 87 | result.completeExceptionally(throwable); 88 | } 89 | }); 90 | 91 | return result; 92 | } 93 | 94 | private static Client makeClient() { 95 | return ClientBuilder.newBuilder().register(Jackson.customJacksonProvider()).build(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/client/HttpRaftClientProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs.client; 17 | 18 | import org.robotninjas.barge.Replica; 19 | import org.robotninjas.barge.jaxrs.HttpReplica; 20 | import org.robotninjas.barge.rpc.RaftClient; 21 | import org.robotninjas.barge.rpc.RaftClientProvider; 22 | 23 | /** 24 | */ 25 | public class HttpRaftClientProvider implements RaftClientProvider{ 26 | 27 | @Override 28 | public RaftClient get(Replica replica) { 29 | return new BargeJaxRsClient(((HttpReplica)replica).getUri()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/EventServlet.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import java.util.Collections; 4 | import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; 5 | import org.eclipse.jetty.websocket.servlet.WebSocketServlet; 6 | import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; 7 | 8 | 9 | /** 10 | */ 11 | public class EventServlet extends WebSocketServlet { 12 | private final WsEventListener events; 13 | 14 | public EventServlet(WsEventListener events) { 15 | this.events = events; 16 | } 17 | 18 | @Override 19 | public void configure(WebSocketServletFactory factory) { 20 | factory.setCreator((req, resp) -> { 21 | 22 | // remove all compressions extensions that might be requested by the browser... 23 | resp.setExtensions(Collections.emptyList()); 24 | 25 | return new EventSocket(events); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/EventSocket.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import org.eclipse.jetty.websocket.api.Session; 4 | import org.eclipse.jetty.websocket.api.WebSocketAdapter; 5 | 6 | 7 | /** 8 | */ 9 | public class EventSocket extends WebSocketAdapter { 10 | 11 | private final WsEventListener events; 12 | private final SessionToListener listenerFactory; 13 | 14 | private volatile Listener listener; 15 | 16 | public EventSocket(WsEventListener events) { 17 | this(events,new SessionToListener()); 18 | } 19 | 20 | public EventSocket(WsEventListener events, SessionToListener listenerFactory) { 21 | this.events = events; 22 | this.listenerFactory = listenerFactory; 23 | } 24 | 25 | @Override 26 | public void onWebSocketConnect(Session sess) { 27 | super.onWebSocketConnect(sess); 28 | 29 | listener = listenerFactory.createListener(sess); 30 | 31 | events.addClient(listener); 32 | } 33 | 34 | @Override 35 | public void onWebSocketText(String message) { 36 | // do nothing 37 | } 38 | 39 | @Override 40 | public void onWebSocketClose(int statusCode, String reason) { 41 | events.removeClient(listener); 42 | 43 | super.onWebSocketClose(statusCode, reason); 44 | } 45 | 46 | @Override 47 | public void onWebSocketError(Throwable cause) { 48 | events.removeClient(listener); 49 | 50 | events.error(this, cause); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/Listener.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | /** 4 | * A simple interface hiding the endpoint from Raft listener. 5 | */ 6 | public interface Listener { 7 | 8 | void send(String message); 9 | } 10 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/SessionToListener.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import com.google.common.base.Charsets; 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | import org.eclipse.jetty.websocket.api.Session; 7 | 8 | 9 | /** 10 | */ 11 | public class SessionToListener { 12 | public Listener createListener(Session sess) { 13 | return new WsSessionListener(sess); 14 | } 15 | 16 | private class WsSessionListener implements Listener { 17 | private final Session session; 18 | 19 | public WsSessionListener(Session session) { 20 | this.session = session; 21 | } 22 | 23 | @Override 24 | public void send(String message) { 25 | 26 | try { 27 | session.getRemote().sendBytes(ByteBuffer.wrap(message.getBytes(Charsets.UTF_8))); 28 | } catch (IOException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "WsSession[" + session.getRemoteAddress() + "]"; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/WsEventListenersModule.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import java.util.List; 4 | import org.robotninjas.barge.state.AbstractListenersModule; 5 | import org.robotninjas.barge.state.RaftProtocolListener; 6 | import org.robotninjas.barge.state.StateTransitionListener; 7 | 8 | 9 | /** 10 | */ 11 | public class WsEventListenersModule extends AbstractListenersModule { 12 | 13 | private final List transitionListeners; 14 | private final List protocolListeners; 15 | 16 | public WsEventListenersModule(List transitionListeners, List protocolListeners) { 17 | this.transitionListeners = transitionListeners; 18 | this.protocolListeners = protocolListeners; 19 | } 20 | 21 | @Override 22 | protected void configureListeners() { 23 | 24 | for (StateTransitionListener listener : transitionListeners) { 25 | bindTransitionListener().toInstance(listener); 26 | } 27 | 28 | for (RaftProtocolListener protocolListener : protocolListeners) { 29 | bindProtocolListener().toInstance(protocolListener); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/org/robotninjas/barge/jaxrs/ws/WsMessage.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | */ 8 | @SuppressWarnings({"UnusedDeclaration", "getters make Jackson happier"}) 9 | public class WsMessage { 10 | 11 | private final String type; 12 | private final Date timestamp; 13 | 14 | public WsMessage(String type) { 15 | this.type = type; 16 | this.timestamp = new Date(); 17 | } 18 | 19 | public String getType() { 20 | return type; 21 | } 22 | 23 | public String getTimestamp() { 24 | return SimpleDateFormat.getDateTimeInstance().format(timestamp); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/java/pack.java: -------------------------------------------------------------------------------- 1 | import com.google.common.io.ByteStreams; 2 | import java.io.File; 3 | import java.io.FileOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.URL; 8 | 9 | 10 | /** 11 | * Simple auto-packing application. 12 | */ 13 | public class pack { 14 | 15 | public static void main(String[] args) throws IOException { 16 | File script = new File("barge.sh"); 17 | 18 | URL url = pack.class.getProtectionDomain().getCodeSource().getLocation(); 19 | 20 | try (OutputStream fos = new FileOutputStream(script)) { 21 | 22 | InputStream headerStream = pack.class.getClassLoader().getResourceAsStream("script-header"); 23 | InputStream inputStream = url.openStream(); 24 | 25 | ByteStreams.copy(headerStream, fos); 26 | ByteStreams.copy(inputStream, fos); 27 | 28 | script.setExecutable(true); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - {%X{self} %X{term} %X{state}} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /barge-jax-rs/src/main/resources/script-header: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # self-extractible script 3 | MYSELF=`which "$0" 2>/dev/null` 4 | [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0" 5 | java=java 6 | if test -n "$JAVA_HOME"; then 7 | java="$JAVA_HOME/bin/java" 8 | fi 9 | exec "$java" $java_args -jar $MYSELF "$@" 10 | exit 1 11 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/HttpClusterConfigTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.net.URI; 21 | import java.net.URISyntaxException; 22 | import org.junit.Test; 23 | 24 | /** 25 | */ 26 | public class HttpClusterConfigTest { 27 | 28 | private final HttpReplica replica; 29 | private final HttpReplica otherReplica; 30 | 31 | public HttpClusterConfigTest() throws URISyntaxException { 32 | replica = new HttpReplica(new URI("http://foo:1234")); 33 | otherReplica = new HttpReplica(new URI("http://foo:1235")); 34 | } 35 | 36 | @Test 37 | public void returnsLocalReplicaGivenStringURIOnConfigWithLocalReplicaWithSameURI() throws Exception { 38 | HttpClusterConfig clusterConfig = new HttpClusterConfig(replica); 39 | 40 | assertThat(clusterConfig.getReplica("http://foo:1234")).isEqualTo(replica); 41 | } 42 | 43 | @Test 44 | public void returnsARemoteReplicaGivenStringURIOnConfigWithRemoteReplicaWithSameURI() throws Exception { 45 | HttpClusterConfig clusterConfig = new HttpClusterConfig(replica, otherReplica); 46 | 47 | assertThat(clusterConfig.getReplica("http://foo:1235")).isEqualTo(otherReplica); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/HttpReplicaTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.net.URI; 21 | import org.junit.Test; 22 | 23 | /** 24 | */ 25 | public class HttpReplicaTest { 26 | 27 | @Test 28 | public void toStringReturnsOnlyURI() throws Exception { 29 | assertThat(new HttpReplica(new URI("http://localhost:1234")).toString()).isEqualTo("http://localhost:1234"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/JdkHttpDeploymentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import java.io.File; 19 | import java.net.URI; 20 | 21 | 22 | /** 23 | */ 24 | public class JdkHttpDeploymentTest extends ServerTest { 25 | 26 | protected RaftJdkServer createServer(int serverIndex, URI[] uris1, File logDir) { 27 | return new RaftJdkServer(serverIndex, uris1, logDir); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/JettyDeploymentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import java.io.File; 19 | import java.net.URI; 20 | import org.robotninjas.barge.jaxrs.ws.RaftJettyServer; 21 | 22 | 23 | /** 24 | */ 25 | public class JettyDeploymentTest extends ServerTest { 26 | 27 | @Override 28 | protected RaftJettyServer createServer(int serverIndex, URI[] uris1, File logDir) { 29 | return new RaftJettyServer(serverIndex, uris1, logDir); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/Logs.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs; 2 | 3 | import java.io.File; 4 | 5 | 6 | /** 7 | * Simple functions for creating "unique" log directories. 8 | */ 9 | public class Logs { 10 | 11 | private static final File target = new File(Logs.class.getResource("/marker").getFile()).getParentFile(); 12 | 13 | 14 | public static File uniqueLog() { 15 | long timestamp = System.nanoTime(); 16 | File log; 17 | 18 | do { 19 | log = new File(target, "log-" + (timestamp++)); 20 | } while (log.exists() && log.isDirectory()); 21 | 22 | return log; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/Model.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import org.robotninjas.barge.api.AppendEntries; 19 | import org.robotninjas.barge.api.AppendEntriesResponse; 20 | import org.robotninjas.barge.api.Entry; 21 | import org.robotninjas.barge.api.RequestVote; 22 | import org.robotninjas.barge.api.RequestVoteResponse; 23 | 24 | /** 25 | */ 26 | public class Model { 27 | 28 | public static final RequestVote vote = RequestVote.newBuilder() 29 | .setCandidateId("id") 30 | .setLastLogIndex(12) 31 | .setLastLogTerm(13) 32 | .setTerm(13) 33 | .build(); 34 | 35 | public static final RequestVoteResponse voteResponse = RequestVoteResponse.newBuilder() 36 | .setVoteGranted(true) 37 | .setTerm(13) 38 | .build(); 39 | 40 | public static final AppendEntries entries = AppendEntries.newBuilder() 41 | .setLeaderId("foo") 42 | .addEntry(Entry.newBuilder() 43 | .setCommand("command".getBytes()) 44 | .setTerm(2).build()) 45 | .addEntry(Entry.newBuilder() 46 | .setCommand("command1".getBytes()) 47 | .setTerm(2).build()) 48 | .setCommitIndex(1) 49 | .setPrevLogIndex(2) 50 | .setPrevLogTerm(3) 51 | .setTerm(3) 52 | .build(); 53 | 54 | public static final AppendEntriesResponse entriesResponse = AppendEntriesResponse.newBuilder() 55 | .setLastLogIndex(12) 56 | .setSuccess(true) 57 | .setTerm(3) 58 | .build(); 59 | } 60 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/MuteJUL.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs; 17 | 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | import org.junit.rules.ExternalResource; 21 | import org.slf4j.bridge.SLF4JBridgeHandler; 22 | 23 | /** 24 | */ 25 | public class MuteJUL extends ExternalResource { 26 | 27 | @Override 28 | protected void before() throws Throwable { 29 | Logger.getLogger("").setLevel(Level.ALL); 30 | SLF4JBridgeHandler.removeHandlersForRootLogger(); 31 | SLF4JBridgeHandler.install(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/ServerTest.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.robotninjas.barge.jaxrs.Logs.uniqueLog; 5 | 6 | import java.io.File; 7 | import java.net.URI; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | import javax.ws.rs.client.Client; 11 | import javax.ws.rs.client.ClientBuilder; 12 | import javax.ws.rs.client.Entity; 13 | import javax.ws.rs.core.MediaType; 14 | import javax.ws.rs.core.Response; 15 | import org.junit.After; 16 | import org.junit.Before; 17 | import org.junit.ClassRule; 18 | import org.junit.Test; 19 | import org.robotninjas.barge.state.Raft; 20 | import org.robotninjas.barge.utils.Prober; 21 | 22 | 23 | /** 24 | */ 25 | public abstract class ServerTest> { 26 | 27 | @ClassRule 28 | public static MuteJUL muteJUL = new MuteJUL(); 29 | 30 | private URI[] uris = new URI[3]; 31 | 32 | private T httpServer1; 33 | private T httpServer2; 34 | private T httpServer3; 35 | 36 | @Before 37 | public void setUp() throws Exception { 38 | Logger.getLogger("").setLevel(Level.ALL); 39 | 40 | uris[0] = new URI("http://localhost:56789/"); 41 | uris[1] = new URI("http://localhost:56790/"); 42 | uris[2] = new URI("http://localhost:56791/"); 43 | 44 | httpServer1 = createServer(0, uris, uniqueLog()).start(56789); 45 | httpServer2 = createServer(1, uris, uniqueLog()).start(56790); 46 | httpServer3 = createServer(2, uris, uniqueLog()).start(56791); 47 | } 48 | 49 | @After 50 | public void tearDown() throws Exception { 51 | httpServer1.stop(); 52 | httpServer2.stop(); 53 | httpServer3.stop(); 54 | 55 | httpServer1.clean(); 56 | httpServer2.clean(); 57 | httpServer3.clean(); 58 | } 59 | 60 | @Test 61 | public void can_commit_data_to_leader_instance() throws Exception { 62 | final Client client = ClientBuilder.newBuilder().register(Jackson.customJacksonProvider()).build(); 63 | 64 | client.target(uris[0]).path("/raft/init").request().post(Entity.json("")); 65 | client.target(uris[1]).path("/raft/init").request().post(Entity.json("")); 66 | client.target(uris[2]).path("/raft/init").request().post(Entity.json("")); 67 | 68 | new Prober(() -> isLeader(client, uris[0]) || isLeader(client, uris[1]) || isLeader(client, uris[2])).probe(10000); 69 | 70 | URI leaderURI = getLeader(client); 71 | 72 | Response result = client.target(leaderURI) 73 | .path("/raft/commit") 74 | .request() 75 | .post(Entity.entity("foo".getBytes(), 76 | MediaType.APPLICATION_OCTET_STREAM)); 77 | 78 | assertThat(result.getStatus()).isEqualTo(204); 79 | } 80 | 81 | protected abstract T createServer(int serverIndex, URI[] uris1, File logDir); 82 | 83 | private URI getLeader(Client client) { 84 | 85 | if (isLeader(client, uris[0])) 86 | return uris[0]; 87 | 88 | if (isLeader(client, uris[1])) 89 | return uris[1]; 90 | 91 | if (isLeader(client, uris[2])) 92 | return uris[2]; 93 | 94 | throw new IllegalStateException("expected one server to be a leader"); 95 | } 96 | 97 | private boolean isLeader(Client client, URI uri) { 98 | return client.target(uri).path("/raft/state").request().get(Raft.StateType.class).equals(Raft.StateType.LEADER); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/client/BargeJaxRsClientTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs.client; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import com.google.common.collect.Sets; 21 | import java.util.Set; 22 | import java.util.concurrent.CompletableFuture; 23 | import java.util.concurrent.TimeUnit; 24 | import javax.ws.rs.Consumes; 25 | import javax.ws.rs.POST; 26 | import javax.ws.rs.Path; 27 | import javax.ws.rs.Produces; 28 | import javax.ws.rs.core.Application; 29 | import javax.ws.rs.core.MediaType; 30 | import org.glassfish.jersey.server.ResourceConfig; 31 | import org.glassfish.jersey.test.JerseyTest; 32 | import org.junit.ClassRule; 33 | import org.junit.Test; 34 | import org.robotninjas.barge.api.AppendEntriesResponse; 35 | import org.robotninjas.barge.api.RequestVoteResponse; 36 | import org.robotninjas.barge.jaxrs.Jackson; 37 | import org.robotninjas.barge.jaxrs.Model; 38 | import org.robotninjas.barge.jaxrs.MuteJUL; 39 | 40 | /** 41 | */ 42 | public class BargeJaxRsClientTest extends JerseyTest { 43 | 44 | @ClassRule 45 | public static MuteJUL muteJUL = new MuteJUL(); 46 | 47 | @Test 48 | public void returnsFutureWithServerResponseWhenRequestingVoteGivenServerAnswers() throws Exception { 49 | BargeJaxRsClient bargeJaxRsClient = new BargeJaxRsClient(getBaseUri(),client().register(Jackson.customJacksonProvider())); 50 | 51 | CompletableFuture future = bargeJaxRsClient.requestVote(Model.vote); 52 | 53 | assertThat(future.get(1, TimeUnit.SECONDS)).isEqualTo(Model.voteResponse); 54 | } 55 | 56 | 57 | @Test 58 | public void returnsFutureWithServerResponseWhenAppendingEntriesGivenServerAnswers() throws Exception { 59 | BargeJaxRsClient bargeJaxRsClient = new BargeJaxRsClient(getBaseUri(),client().register(Jackson.customJacksonProvider())); 60 | 61 | CompletableFuture future = bargeJaxRsClient.appendEntries(Model.entries); 62 | 63 | assertThat(future.get(1, TimeUnit.SECONDS)).isEqualTo(Model.entriesResponse); 64 | } 65 | 66 | 67 | @Override 68 | protected Application configure() { 69 | ResourceConfig resourceConfig = ResourceConfig.forApplication(new Application() { 70 | @Override 71 | public Set getSingletons() { 72 | return Sets.newHashSet((Object) new DummyBargeServer()); 73 | } 74 | }); 75 | 76 | resourceConfig.register(Jackson.customJacksonProvider()); 77 | 78 | return resourceConfig; 79 | } 80 | 81 | @Path("/raft") 82 | @Consumes(MediaType.APPLICATION_JSON) 83 | @Produces(MediaType.APPLICATION_JSON) 84 | public static class DummyBargeServer { 85 | 86 | @Path("/vote") 87 | @POST 88 | public RequestVoteResponse vote() { 89 | return Model.voteResponse; 90 | } 91 | 92 | @Path("/entries") 93 | @POST 94 | public AppendEntriesResponse entries() { 95 | return Model.entriesResponse; 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/client/HttpRaftClientProviderTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.robotninjas.barge.jaxrs.client; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.net.URI; 21 | import org.junit.Test; 22 | import org.robotninjas.barge.jaxrs.HttpReplica; 23 | import org.robotninjas.barge.rpc.RaftClient; 24 | 25 | /** 26 | */ 27 | public class HttpRaftClientProviderTest { 28 | 29 | @Test 30 | public void providesAClientToAccessTheReplicaURLGivenAHttpReplica() throws Exception { 31 | HttpRaftClientProvider provider = new HttpRaftClientProvider(); 32 | 33 | RaftClient client = provider.get(new HttpReplica(new URI("http://localhost:1234"))); 34 | 35 | assertThat(client).isInstanceOf(BargeJaxRsClient.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/ws/EventSocketTest.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.verify; 5 | import static org.mockito.Mockito.when; 6 | 7 | import org.eclipse.jetty.websocket.api.Session; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | public class EventSocketTest { 12 | 13 | private final WsEventListener events = mock(WsEventListener.class); 14 | private final Session session = mock(Session.class); 15 | private final Listener listener = mock(Listener.class); 16 | private final SessionToListener listenerFactory= mock(SessionToListener.class); 17 | 18 | private final EventSocket socket = new EventSocket(events, listenerFactory); 19 | 20 | @Before 21 | public void setup(){ 22 | when(listenerFactory.createListener(session)).thenReturn(listener); 23 | } 24 | 25 | @Test 26 | public void registers_a_listener_when_sockets_open() throws Exception { 27 | socket.onWebSocketConnect(session); 28 | 29 | verify(events).addClient(listener); 30 | } 31 | 32 | @Test 33 | public void removes_registered_session_when_sockets_close() throws Exception { 34 | socket.onWebSocketConnect(session); 35 | socket.onWebSocketClose(200, "closing"); 36 | 37 | verify(events).removeClient(listener); 38 | } 39 | 40 | @Test 41 | public void removes_registered_session_and_notifies_exception_when_sockets_has_error() throws Exception { 42 | Exception error = new Exception("error"); 43 | 44 | socket.onWebSocketConnect(session); 45 | socket.onWebSocketError(error); 46 | 47 | verify(events).removeClient(listener); 48 | verify(events).error(socket, error); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /barge-jax-rs/src/test/java/org/robotninjas/barge/jaxrs/ws/RaftJettyServerTest.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.jaxrs.ws; 2 | 3 | import static org.robotninjas.barge.jaxrs.Logs.uniqueLog; 4 | 5 | import com.google.common.base.Predicates; 6 | import java.net.URI; 7 | import java.util.Queue; 8 | import java.util.concurrent.LinkedBlockingQueue; 9 | import java.util.regex.Pattern; 10 | import javax.ws.rs.client.Client; 11 | import javax.ws.rs.client.ClientBuilder; 12 | import javax.ws.rs.client.Entity; 13 | import org.eclipse.jetty.websocket.api.Session; 14 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; 15 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; 16 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; 17 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; 18 | import org.eclipse.jetty.websocket.client.WebSocketClient; 19 | import org.junit.After; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.robotninjas.barge.jaxrs.Jackson; 23 | import org.robotninjas.barge.utils.Prober; 24 | 25 | 26 | /** 27 | */ 28 | public class RaftJettyServerTest { 29 | 30 | private RaftJettyServer server; 31 | private URI uri; 32 | 33 | private final WebSocketClient wsClient = new WebSocketClient(); 34 | private final Client client = ClientBuilder.newBuilder().register(Jackson.customJacksonProvider()).build(); 35 | 36 | @Before 37 | public void setUp() throws Exception { 38 | this.server = new RaftJettyServer(0, new URI[] { new URI("http://localhost:12345") }, 39 | uniqueLog()).start(0); 40 | this.uri = server.getPort(); 41 | 42 | wsClient.start(); 43 | } 44 | 45 | @After 46 | public void tearDown() throws Exception { 47 | this.wsClient.stop(); 48 | this.server.stop(); 49 | } 50 | 51 | @Test 52 | public void receives_START_event_through_web_socket_when_connecting_to_events_endpoint() throws Exception { 53 | URI wsEvents = new URI("ws://" + uri.getHost() + ":" + uri.getPort() + "/events"); 54 | 55 | final Queue messages = new LinkedBlockingQueue<>(); 56 | final EventClientSocket socket = new EventClientSocket(messages); 57 | 58 | ClientUpgradeRequest request = new ClientUpgradeRequest(); 59 | wsClient.connect(socket, wsEvents, request); 60 | 61 | client.target(uri).path("/raft/init").request().post(Entity.json("")); 62 | 63 | new Prober(() -> socket.messages.stream().anyMatch(Predicates.contains(Pattern.compile(".*FOLLOWER.*"))::apply)).probe(10000); 64 | } 65 | 66 | 67 | @SuppressWarnings("UnusedDeclaration") 68 | @WebSocket(maxTextMessageSize = 64 * 1024) 69 | public static class EventClientSocket { 70 | 71 | private final Queue messages; 72 | 73 | @SuppressWarnings("unused") 74 | private Session session; 75 | 76 | public EventClientSocket(Queue messages) { 77 | this.messages = messages; 78 | } 79 | 80 | @OnWebSocketMessage 81 | public void onMessage(byte[] buffer, int offset, int size) { 82 | String message = new String(buffer, offset, size); 83 | this.messages.add(message); 84 | } 85 | 86 | @OnWebSocketConnect 87 | public void onConnect(Session session) { 88 | this.session = session; 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /barge-jax-rs/src/test/resources/marker: -------------------------------------------------------------------------------- 1 | # a marker file -------------------------------------------------------------------------------- /barge-rpc-proto/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | barge 7 | org.robotninjas.barge 8 | 0.1.0-alpha2-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | barge-rpc-proto 13 | Main entry point to instantiate a fully functional barge service 14 | 15 | 16 | 17 | 18 | org.robotninjas.barge 19 | barge-core 20 | 21 | 22 | 23 | org.robotninjas 24 | protorpc 25 | 26 | 27 | 28 | commons-pool 29 | commons-pool 30 | 31 | 32 | 33 | com.google.guava 34 | guava 35 | 36 | 37 | 38 | com.google.inject 39 | guice 40 | 41 | 42 | 43 | org.jetlang 44 | jetlang 45 | 46 | 47 | 48 | net.javacrumbs.future-converter 49 | future-converter-java8-guava 50 | 51 | 52 | 53 | 54 | com.google.code.findbugs 55 | jsr305 56 | 57 | 58 | 59 | org.assertj 60 | assertj-core 61 | 62 | 63 | 64 | junit 65 | junit 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/NettyClusterConfig.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import static com.google.common.collect.Iterables.unmodifiableIterable; 4 | import static com.google.common.collect.Lists.newArrayList; 5 | 6 | import com.google.common.base.MoreObjects; 7 | import com.google.common.base.Objects; 8 | import com.google.common.collect.Iterables; 9 | 10 | public class NettyClusterConfig implements ClusterConfig { 11 | 12 | private final NettyReplica local; 13 | private final Iterable remote; 14 | 15 | NettyClusterConfig(NettyReplica local, Iterable remote) { 16 | this.local = local; 17 | this.remote = remote; 18 | } 19 | 20 | public static NettyClusterConfig from(NettyReplica local, Replica... remote) { 21 | return new NettyClusterConfig(local, newArrayList(remote)); 22 | } 23 | 24 | public static ClusterConfig from(NettyReplica local, Iterable remote) { 25 | return new NettyClusterConfig(local, remote); 26 | } 27 | 28 | @Override 29 | public NettyReplica local() { 30 | return local; 31 | } 32 | 33 | @Override 34 | public Iterable remote() { 35 | return unmodifiableIterable(remote); 36 | } 37 | 38 | @Override 39 | public NettyReplica getReplica(String info) { 40 | return NettyReplica.fromString(info); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | Iterable all = Iterables.concat(newArrayList(local), remote); 46 | return Objects.hashCode((Object[]) Iterables.toArray(all, Replica.class)); 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | 52 | if (o == this) { 53 | return true; 54 | } 55 | 56 | if (!(o instanceof NettyClusterConfig)) { 57 | return false; 58 | } 59 | 60 | NettyClusterConfig other = (NettyClusterConfig) o; 61 | return local.equals(other.local) && 62 | Iterables.elementsEqual(remote, other.remote); 63 | 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return MoreObjects.toStringHelper(this) 69 | .add("local", local) 70 | .add("remote", remote) 71 | .toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/NettyRaftModule.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import com.google.inject.PrivateModule; 4 | import java.io.File; 5 | import org.robotninjas.barge.state.AbstractListenersModule; 6 | 7 | 8 | public class NettyRaftModule extends PrivateModule { 9 | 10 | private final NettyClusterConfig config; 11 | private final File logDir; 12 | private final StateMachine stateMachine; 13 | private final long timeout; 14 | 15 | public NettyRaftModule(NettyClusterConfig config, File logDir, StateMachine stateMachine, long timeout) { 16 | this.config = config; 17 | this.logDir = logDir; 18 | this.stateMachine = stateMachine; 19 | this.timeout = timeout; 20 | } 21 | 22 | @Override 23 | protected void configure() { 24 | install(new AbstractListenersModule() { 25 | @Override 26 | protected void configureListeners() { 27 | } 28 | }); 29 | 30 | install(RaftCoreModule.builder() 31 | .withTimeout(timeout) 32 | .withConfig(config) 33 | .withLogDir(logDir) 34 | .withStateMachine(stateMachine) 35 | .build()); 36 | 37 | install(new RaftProtoRpcModule(config.local())); 38 | 39 | bind(NettyRaftService.class); 40 | expose(NettyRaftService.class); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/NettyReplica.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import com.google.common.base.Objects; 22 | import com.google.common.net.HostAndPort; 23 | import java.net.InetAddress; 24 | import java.net.InetSocketAddress; 25 | import java.net.SocketAddress; 26 | import java.net.UnknownHostException; 27 | import javax.annotation.Nonnull; 28 | import javax.annotation.concurrent.Immutable; 29 | import javax.annotation.concurrent.ThreadSafe; 30 | 31 | @Immutable 32 | @ThreadSafe 33 | public class NettyReplica implements Replica { 34 | 35 | private final InetSocketAddress address; 36 | 37 | NettyReplica(@Nonnull InetSocketAddress address) { 38 | this.address = checkNotNull(address); 39 | } 40 | 41 | @Nonnull 42 | public static NettyReplica fromString(@Nonnull String info) { 43 | try { 44 | checkNotNull(info); 45 | HostAndPort hostAndPort = HostAndPort.fromString(info); 46 | InetAddress addr = InetAddress.getByName(hostAndPort.getHost()); 47 | InetSocketAddress saddr = new InetSocketAddress(addr, hostAndPort.getPort()); 48 | return new NettyReplica(saddr); 49 | } catch (UnknownHostException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | 54 | public SocketAddress address() { 55 | return address; 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return Objects.hashCode(address()); 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | 66 | if (o == this) { 67 | return true; 68 | } 69 | 70 | if (o instanceof NettyReplica) { 71 | NettyReplica other = (NettyReplica) o; 72 | return Objects.equal(address(), other.address()); 73 | } 74 | 75 | return false; 76 | 77 | } 78 | 79 | @Nonnull 80 | @Override 81 | public String toString() { 82 | return address.getAddress().getHostName() + ":" + address.getPort(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/RaftProtoRpcModule.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import com.google.inject.PrivateModule; 4 | import io.netty.channel.nio.NioEventLoopGroup; 5 | import java.util.Optional; 6 | import org.robotninjas.barge.rpc.RaftClientProvider; 7 | import org.robotninjas.barge.rpc.RpcModule; 8 | import org.robotninjas.protobuf.netty.server.RpcServer; 9 | 10 | class RaftProtoRpcModule extends PrivateModule { 11 | 12 | private final NettyReplica localEndpoint; 13 | private Optional eventLoopGroup = Optional.empty(); 14 | 15 | public RaftProtoRpcModule(NettyReplica localEndpoint) { 16 | this.localEndpoint = localEndpoint; 17 | } 18 | 19 | @Override 20 | protected void configure() { 21 | final NioEventLoopGroup eventLoop = initializeEventLoop(); 22 | 23 | install(new RpcModule(localEndpoint.address(), eventLoop)); 24 | 25 | expose(RpcServer.class); 26 | expose(RaftClientProvider.class); 27 | } 28 | 29 | private NioEventLoopGroup initializeEventLoop() { 30 | final NioEventLoopGroup eventLoop; 31 | 32 | if (eventLoopGroup.isPresent()) { 33 | eventLoop = eventLoopGroup.get(); 34 | } else { 35 | eventLoop = new NioEventLoopGroup(1); 36 | Runtime.getRuntime().addShutdownHook( 37 | new Thread(eventLoop::shutdownGracefully) 38 | ); 39 | } 40 | return eventLoop; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/RaftService.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | /** 6 | * An instance of a set of replica managed through Raft protocol. 7 | *

8 | * A {@link RaftService} instance is constructed by specific builders depending on: Communication protocol used, 9 | * persistent storage, network characteristics... 10 | *

11 | */ 12 | public interface RaftService { 13 | 14 | /** 15 | * Asynchronously executes an operation on the state machine managed by barge. 16 | *

17 | * When the result becomes available, the operation is guaranteed to have been committed to the Raft 18 | * cluster in such a way that it is permanent and will be seen, eventually, by all present and 19 | * future members of the cluster. 20 | *

21 | * 22 | * @param operation an arbitrary operation to be sent to the state machine managed by Raft. 23 | * @return the result of executing the operation, wrapped in a {@link CompletableFuture}, that can be retrieved 24 | * at a later point in time. 25 | * @throws org.robotninjas.barge.RaftException 26 | */ 27 | CompletableFuture commitAsync(byte[] operation) throws RaftException; 28 | 29 | /** 30 | * Synchronously executes and operation on the state machine managed by barge. 31 | *

32 | * This method is semantically equivalent to: 33 | *

34 | *
35 |    *     commitAsync(op).get();
36 |    * 
37 | * 38 | * @param operation an arbitrary operation to be sent to the state machine managed by Raft. 39 | * @return the result of executing the operation, as returned by the state machine. 40 | * @throws RaftException 41 | * @throws InterruptedException if current thread is interrupted while waiting for the result to be available. 42 | */ 43 | Object commit(byte[] operation) throws RaftException, InterruptedException; 44 | } 45 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/RaftServiceEndpoint.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import static com.google.common.base.Strings.nullToEmpty; 4 | import static org.robotninjas.barge.proto.RaftProto.AppendEntries; 5 | import static org.robotninjas.barge.proto.RaftProto.AppendEntriesResponse; 6 | import static org.robotninjas.barge.proto.RaftProto.RequestVote; 7 | import static org.robotninjas.barge.proto.RaftProto.RequestVoteResponse; 8 | 9 | import com.google.protobuf.RpcCallback; 10 | import com.google.protobuf.RpcController; 11 | import javax.annotation.Nonnull; 12 | import org.robotninjas.barge.proto.RaftProto; 13 | import org.robotninjas.barge.state.Raft; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | class RaftServiceEndpoint implements RaftProto.RaftService.Interface { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftServiceEndpoint.class); 20 | 21 | private final Raft ctx; 22 | 23 | public RaftServiceEndpoint(Raft ctx) { 24 | this.ctx = ctx; 25 | } 26 | 27 | @Override 28 | public void requestVote(@Nonnull final RpcController controller, @Nonnull final RequestVote request, @Nonnull final RpcCallback done) { 29 | try { 30 | done.run(ProtoUtils.convert(ctx.requestVote(ProtoUtils.convert(request)))); 31 | } catch (Exception e) { 32 | LOGGER.debug("Exception caught servicing RequestVote", e); 33 | controller.setFailed(nullToEmpty(e.getMessage())); 34 | done.run(null); 35 | } 36 | } 37 | 38 | @Override 39 | public void appendEntries(@Nonnull final RpcController controller, @Nonnull final AppendEntries request, @Nonnull final RpcCallback done) { 40 | try { 41 | done.run(ProtoUtils.convert(ctx.appendEntries(ProtoUtils.convert(request)))); 42 | } catch (Exception e) { 43 | LOGGER.debug("Exception caught servicing AppendEntries", e); 44 | controller.setFailed(nullToEmpty(e.getMessage())); 45 | done.run(null); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/rpc/ProtoRpcRaftClientProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.rpc; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import java.util.concurrent.CompletableFuture; 22 | import javax.annotation.Nonnull; 23 | import javax.annotation.concurrent.Immutable; 24 | import javax.inject.Inject; 25 | import org.apache.commons.pool.KeyedObjectPool; 26 | import org.apache.commons.pool.PoolUtils; 27 | import org.apache.commons.pool.impl.GenericKeyedObjectPool; 28 | import org.robotninjas.barge.Replica; 29 | import org.robotninjas.protobuf.netty.client.NettyRpcChannel; 30 | import org.robotninjas.protobuf.netty.client.RpcClient; 31 | 32 | @Immutable 33 | class ProtoRpcRaftClientProvider implements RaftClientProvider { 34 | 35 | private static final GenericKeyedObjectPool.Config config; 36 | 37 | static { 38 | config = new GenericKeyedObjectPool.Config(); 39 | config.maxActive = 1; 40 | config.maxIdle = -1; 41 | config.testOnBorrow = true; 42 | config.testOnReturn = true; 43 | config.whenExhaustedAction = GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL; 44 | } 45 | 46 | private final KeyedObjectPool> connectionPools; 47 | 48 | @Inject 49 | public ProtoRpcRaftClientProvider(@Nonnull RpcClient client) { 50 | RpcChannelFactory channelFactory = new RpcChannelFactory(client); 51 | this.connectionPools = new GenericKeyedObjectPool<>(channelFactory, config); 52 | } 53 | 54 | @Nonnull 55 | public RaftClient get(@Nonnull Replica replica) { 56 | checkNotNull(replica); 57 | return new ProtoRpcRaftClient(PoolUtils.adapt(connectionPools, replica)); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/rpc/RpcChannelFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.rpc; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import com.google.common.base.Preconditions; 22 | import com.google.common.util.concurrent.Futures; 23 | import java.util.concurrent.CompletableFuture; 24 | import javax.annotation.Nonnull; 25 | import javax.annotation.concurrent.Immutable; 26 | import net.javacrumbs.futureconverter.java8guava.FutureConverter; 27 | import org.apache.commons.pool.BaseKeyedPoolableObjectFactory; 28 | import org.robotninjas.barge.NettyReplica; 29 | import org.robotninjas.protobuf.netty.client.NettyRpcChannel; 30 | import org.robotninjas.protobuf.netty.client.RpcClient; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | @Immutable 35 | class RpcChannelFactory extends BaseKeyedPoolableObjectFactory> { 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcChannelFactory.class); 38 | 39 | private final RpcClient client; 40 | 41 | public RpcChannelFactory(@Nonnull RpcClient client) { 42 | this.client = checkNotNull(client); 43 | } 44 | 45 | @Override 46 | public CompletableFuture makeObject(Object key) throws Exception { 47 | Preconditions.checkArgument(key instanceof NettyReplica); 48 | NettyReplica replica = (NettyReplica) key; 49 | return FutureConverter.toCompletableFuture(client.connectAsync(replica.address())); 50 | } 51 | 52 | @Override 53 | public void destroyObject(Object key, CompletableFuture obj) throws Exception { 54 | if (obj.isDone() && !obj.isCancelled()) { 55 | obj.get().close(); 56 | } else { 57 | obj.cancel(false); 58 | } 59 | } 60 | 61 | @Override 62 | public boolean validateObject(Object key, CompletableFuture obj) { 63 | return !obj.isDone() || (obj.isDone() && Futures.getUnchecked(obj).isOpen()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/java/org/robotninjas/barge/rpc/RpcModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.robotninjas.barge.rpc; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | import com.google.inject.PrivateModule; 22 | import io.netty.channel.nio.NioEventLoopGroup; 23 | import java.net.SocketAddress; 24 | import javax.annotation.Nonnull; 25 | import org.robotninjas.protobuf.netty.client.RpcClient; 26 | import org.robotninjas.protobuf.netty.server.RpcServer; 27 | 28 | public class RpcModule extends PrivateModule { 29 | 30 | private final SocketAddress saddr; 31 | private final NioEventLoopGroup eventLoopGroup; 32 | 33 | public RpcModule(@Nonnull SocketAddress saddr, NioEventLoopGroup eventLoopGroup) { 34 | this.saddr = checkNotNull(saddr); 35 | this.eventLoopGroup = checkNotNull(eventLoopGroup); 36 | } 37 | 38 | @Override 39 | protected void configure() { 40 | 41 | bind(NioEventLoopGroup.class) 42 | .toInstance(eventLoopGroup); 43 | 44 | RpcServer rpcServer = new RpcServer(eventLoopGroup, saddr); 45 | 46 | bind(RpcServer.class) 47 | .toInstance(rpcServer); 48 | expose(RpcServer.class); 49 | 50 | bind(RaftClientProvider.class) 51 | .to(ProtoRpcRaftClientProvider.class) 52 | .asEagerSingleton(); 53 | 54 | expose(RaftClientProvider.class); 55 | 56 | bind(RpcClient.class) 57 | .toInstance(new RpcClient(eventLoopGroup)); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/proto/entry.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | option java_package = "org.robotninjas.barge.proto"; 18 | option java_outer_classname = "RaftEntry"; 19 | 20 | message Entry { 21 | required int64 term = 1; 22 | required bytes command = 2; 23 | } -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/proto/log.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | option java_package = "org.robotninjas.barge.proto"; 18 | 19 | option java_outer_classname = "LogProto"; 20 | 21 | import "entry.proto"; 22 | 23 | message Append { 24 | required int64 index = 1; 25 | required Entry entry = 2; 26 | } 27 | 28 | message Membership { 29 | required int64 index = 1; 30 | required int64 term = 2; 31 | repeated string members = 3; 32 | } 33 | 34 | message Vote { 35 | optional string voted_for = 2; 36 | } 37 | 38 | message Term { 39 | required int64 term = 1; 40 | } 41 | 42 | message Commit { 43 | required int64 index = 1; 44 | } 45 | 46 | message Snapshot { 47 | required int64 last_included_index = 1; 48 | required int64 last_included_term = 2; 49 | required string snapshot_file = 3; 50 | } 51 | 52 | message JournalEntry { 53 | optional Append append = 2; 54 | optional Membership membership = 3; 55 | optional Vote vote = 4; 56 | optional Term term = 5; 57 | optional Commit commit = 6; 58 | optional Snapshot snapshot = 7; 59 | } -------------------------------------------------------------------------------- /barge-rpc-proto/src/main/proto/raft.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013 David Rusek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | option java_package = "org.robotninjas.barge.proto"; 18 | option java_generic_services = true; 19 | option java_outer_classname = "RaftProto"; 20 | 21 | import "entry.proto"; 22 | 23 | message RequestVote { 24 | required int64 term = 1; 25 | required string candidate_id = 2; 26 | required int64 last_log_index = 3; 27 | required int64 last_log_term = 4; 28 | } 29 | 30 | message RequestVoteResponse { 31 | required int64 term = 1; 32 | required bool vote_granted = 2; 33 | } 34 | 35 | message AppendEntries { 36 | required int64 term = 1; 37 | required string leader_id = 2; 38 | required int64 prev_log_index = 3; 39 | required int64 prev_log_term = 4; 40 | required int64 commit_index = 5; 41 | repeated Entry entries = 6; 42 | } 43 | 44 | message AppendEntriesResponse { 45 | 46 | message TermInfo { 47 | required int64 term = 1; 48 | required int64 begin_index = 2; 49 | required int64 end_index = 3; 50 | } 51 | 52 | required int64 term = 1; 53 | required bool success = 2; 54 | optional int64 last_log_index = 3; 55 | optional TermInfo term_info = 4; 56 | 57 | } 58 | 59 | message InstallSnapshot { 60 | required int64 term = 1; 61 | required int64 last_included_index = 2; 62 | required int64 last_included_term = 3; 63 | required bytes snapshot_data = 4; 64 | } 65 | 66 | message InstallSnapshotResponse { 67 | required int64 term = 1; 68 | required bool success = 2; 69 | } 70 | 71 | service RaftService { 72 | rpc requestVote (RequestVote) returns (RequestVoteResponse); 73 | rpc appendEntries (AppendEntries) returns (AppendEntriesResponse); 74 | } 75 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/test/java/org/robotninjas/barge/NettyRaftServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import java.io.File; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | 7 | public class NettyRaftServiceTest { 8 | 9 | private static final File target = new File(System.getProperty("basedir", "."), "target"); 10 | 11 | @Rule 12 | public GroupOfCounters counters = new GroupOfCounters(3, target); 13 | 14 | @Test(timeout = 30000) 15 | public void canRun3RaftInstancesReachingCommonState() throws Exception { 16 | counters.waitForLeaderElection(); 17 | 18 | int increments = 10; 19 | 20 | for (int i = 0; i < increments; i++) { 21 | counters.commitToLeader(new byte[]{1}); 22 | } 23 | 24 | counters.waitAllToReachValue(increments, 10000); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/test/java/org/robotninjas/barge/SimpleCounterMachine.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.util.List; 9 | import javax.annotation.Nonnull; 10 | import org.robotninjas.barge.utils.Files; 11 | import org.robotninjas.barge.utils.Prober; 12 | 13 | 14 | public class SimpleCounterMachine implements StateMachine { 15 | 16 | private final int id; 17 | private final List replicas; 18 | private final GroupOfCounters groupOfCounters; 19 | 20 | private int counter; 21 | private File logDirectory; 22 | private NettyRaftService service; 23 | 24 | public SimpleCounterMachine(int id, List replicas, GroupOfCounters groupOfCounters) { 25 | checkArgument((id >= 0) && (id < replicas.size()), "replica id " + id + " should be between 0 and " + replicas.size()); 26 | 27 | this.groupOfCounters = groupOfCounters; 28 | this.id = id; 29 | this.replicas = replicas; 30 | } 31 | 32 | @Override 33 | public Object applyOperation(@Nonnull ByteBuffer entry) { 34 | return this.counter++; 35 | } 36 | 37 | public void startRaft() { 38 | int clusterSize = replicas.size(); 39 | NettyReplica[] configuration = new NettyReplica[clusterSize - 1]; 40 | 41 | for (int i = 0; i < (clusterSize - 1); i++) { 42 | configuration[i] = replicas.get((id + i + 1) % clusterSize); 43 | } 44 | 45 | NettyClusterConfig config1 = NettyClusterConfig.from(replicas.get(id % clusterSize), configuration); 46 | 47 | NettyRaftService service1 = NettyRaftService.newBuilder(config1) 48 | .logDir(logDirectory) 49 | .timeout(500) 50 | .transitionListener(groupOfCounters) 51 | .build(this); 52 | 53 | service1.startAsync().awaitRunning(); 54 | this.service = service1; 55 | } 56 | 57 | public File makeLogDirectory(File parentDirectory) throws IOException { 58 | this.logDirectory = new File(parentDirectory, "log" + id); 59 | 60 | if (logDirectory.exists()) { 61 | Files.delete(logDirectory); 62 | } 63 | 64 | if (!logDirectory.exists() && !logDirectory.mkdirs()) { 65 | throw new IllegalStateException("cannot create log directory " + logDirectory); 66 | } 67 | 68 | return logDirectory; 69 | } 70 | 71 | 72 | public void commit(byte[] bytes) throws InterruptedException, RaftException { 73 | service.commit(bytes); 74 | } 75 | 76 | public void stop() { 77 | service.stopAsync().awaitTerminated(); 78 | } 79 | 80 | public void deleteLogDirectory() { 81 | Files.delete(logDirectory); 82 | } 83 | 84 | /** 85 | * Wait at most {@code timeout} for this counter to reach value {@code increments}. 86 | * 87 | * @param increments value expected for counter. 88 | * @param timeout timeout in ms. 89 | * @throws IllegalStateException if {@code expected} is not reached at end of timeout. 90 | */ 91 | public void waitForValue(final int increments, long timeout) { 92 | new Prober(() -> increments == counter).probe(timeout); 93 | } 94 | 95 | public boolean isLeader() { 96 | return service.isLeader(); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /barge-rpc-proto/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - {%X{self} %X{term} %X{state}} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /barge-tools/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | barge 5 | org.robotninjas.barge 6 | 0.1.0-alpha2-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | barge-tools 11 | 12 | 13 | 14 | org.robotninjas.barge 15 | barge-core 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-assembly-plugin 30 | 31 | 32 | src/main/assembly/assembly.xml 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /barge-tools/src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | bundle 3 | 4 | tar.gz 5 | dir 6 | 7 | false 8 | 9 | 10 | src/main/bin 11 | bin 12 | 13 | *.sh 14 | 15 | unix 16 | 0744 17 | 18 | 19 | 20 | 21 | runtime 22 | true 23 | lib 24 | false 25 | 26 | 27 | -------------------------------------------------------------------------------- /barge-tools/src/main/bin/logtool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Find Java 4 | if [ "$JAVA_HOME" = "" ] ; then 5 | JAVA="java -server" 6 | else 7 | JAVA="$JAVA_HOME/bin/java -server" 8 | fi 9 | 10 | PREFIX=$( echo `dirname $0`/.. ) 11 | LIB_DIR=$PREFIX/lib 12 | 13 | # Set Java options 14 | if [ "$JAVA_OPTIONS" = "" ] ; then 15 | JAVA_OPTIONS=" \ 16 | -XX:+UseConcMarkSweepGC \ 17 | -d64" 18 | fi 19 | 20 | export BASE_DIR=$* 21 | 22 | # Launch the application 23 | cd $PREFIX 24 | 25 | export PREFIX 26 | export CLASSPATH=$( echo $LIB_DIR/*.jar . | sed 's/ /:/g') 27 | exec $JAVA $JAVA_OPTIONS org.robotninjas.barge.tools.LogTool $* -------------------------------------------------------------------------------- /barge-tools/src/main/java/org/robotninjas/barge/tools/LogTool.java: -------------------------------------------------------------------------------- 1 | package org.robotninjas.barge.tools; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import journal.io.api.Journal; 9 | import journal.io.api.JournalBuilder; 10 | import journal.io.api.Location; 11 | import org.robotninjas.barge.api.Append; 12 | import org.robotninjas.barge.api.Commit; 13 | import org.robotninjas.barge.api.JournalEntry; 14 | import org.robotninjas.barge.api.Membership; 15 | import org.robotninjas.barge.api.Snapshot; 16 | import org.robotninjas.barge.api.Term; 17 | import org.robotninjas.barge.api.Vote; 18 | 19 | /** 20 | * For now this is just a simple program to print the contents of the log. I envision 21 | * this turning into a generic tool for working with the log. 22 | * 23 | */ 24 | public class LogTool { 25 | 26 | private enum Type {EMPTY, APPEND, COMMIT, MEMBERSHIP, SNAPSHOT, TERM, VOTE} 27 | 28 | public static void main(String... args) throws IOException { 29 | 30 | Journal journal = JournalBuilder.of(new File(args[0])).open(); 31 | 32 | long term = 0; 33 | long index = 0; 34 | long committed = 0; 35 | List membership = Collections.emptyList(); 36 | Optional lastVotedFor = Optional.empty(); 37 | Type lastEntryType = Type.EMPTY; 38 | 39 | for (Location loc : journal.redo()) { 40 | 41 | byte[] rawEntry = journal.read(loc, Journal.ReadType.ASYNC); 42 | JournalEntry entry = JournalEntry.parseFrom(rawEntry); 43 | 44 | if (entry.hasAppend()) { 45 | 46 | Append a = entry.getAppend(); 47 | index = a.getIndex(); 48 | term = a.getEntry().getTerm(); 49 | lastEntryType = Type.APPEND; 50 | 51 | } else if (entry.hasCommit()) { 52 | 53 | Commit c = entry.getCommit(); 54 | committed = c.getIndex(); 55 | lastEntryType = Type.COMMIT; 56 | 57 | } else if (entry.hasMembership()) { 58 | 59 | Membership m = entry.getMembership(); 60 | membership = m.getMembersList(); 61 | lastEntryType = Type.MEMBERSHIP; 62 | 63 | } else if (entry.hasSnapshot()) { 64 | 65 | Snapshot s = entry.getSnapshot(); 66 | index = s.getLastIncludedIndex(); 67 | term = s.getLastIncludedTerm(); 68 | lastEntryType = Type.SNAPSHOT; 69 | 70 | } else if (entry.hasTerm()) { 71 | 72 | Term t = entry.getTerm(); 73 | term = t.getTerm(); 74 | lastEntryType = Type.TERM; 75 | 76 | } else if (entry.hasVote()) { 77 | 78 | Vote v = entry.getVote(); 79 | lastVotedFor = Optional.ofNullable(v.getVotedFor()); 80 | lastEntryType = Type.VOTE; 81 | 82 | System.out.println("Vote: " + lastVotedFor.orElse(null)); 83 | 84 | } 85 | 86 | System.out.println(lastEntryType.toString() + " " + "term: " + term + ", index: " + index + ", committed: " + committed); 87 | } 88 | 89 | journal.close(); 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /barge-ui/README.md: -------------------------------------------------------------------------------- 1 | # Minimalist Demo UI 2 | 3 | This HTML UI is meant to display in real time the state of each (configured) node in a Raft cluster: 4 | * The node's name, IP address and port 5 | * The node's state: active, down 6 | * The list of messages output by this node along with their timestamp. 7 | 8 | This interface is currently hardwired to track 3 nodes on localhost listening on port 56789, 56790, 56791. 9 | 10 | # Developing 11 | 12 | This UI is developed using [clojurescript]() as language and [om](https://github.com/swannodette/om) framework which is a cljs wrapper 13 | over [Facebook's React](http://facebook.github.io/react/). Follow the instructions below to get a development environment using 14 | [LightTable](http://lighttable.com). 15 | 16 | * Open LightTable (note that if you are behind a proxy, you need to setup the `http_proxy` environment variable for the 17 | LightTable process. I could not find how to do this on Mac OS X so this means I need to launch the `.app` from the command-line) 18 | * `Open Folder` and select *barge-ui* 19 | * Click on the `test.html` file to open it 20 | * Type `Cmd+Enter` in the file: This should open a new tab displaying embeded LightTable browser. 21 | * In the directory `barge-ui`, starts clojurescript compiler in `auto` mode to activate compilation on each change. The following 22 | command activate cljsbuild compilation for the `dev` build (there is a `prod` build which is currently unused): 23 | 24 | > $ lein cljsbuild auto dev 25 | > Compiling ClojureScript. 26 | > Compiling "app.js" from ["src"]... 27 | > Successfully compiled "app.js" in 3.752 seconds. 28 | 29 | * Refresh the embedded browser, you should see the skeletal UI for 3 nodes displayed 30 | * You can now open and hack the `src/barge/core.cljs`: Saving it will trigger recompilation. To update display, either refresh 31 | browser hitting `Cmd + R`. Evaluating a form in the `core.cljs` will be done in the context of the embedded browser, so it may impact immediately 32 | the UI. I noticed this was not always the case, especially for top-level OM components that do not get re-rendered even if code is 33 | updated. 34 | 35 | The styling part is done using [Pure](http://purecss.io), a Lightweight alternative to Twitter's [bootstrap]() that's convenient for such a simple UI. 36 | -------------------------------------------------------------------------------- /barge-ui/css/bootstrap/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: black; 3 | font-size: 16px; 4 | margin: 0px auto 0px auto; 5 | width: 900px; 6 | } 7 | 8 | div#header { 9 | border-bottom: 2px solid black; 10 | margin-bottom: 30px; 11 | padding: 12px 0px 12px 0px; 12 | } 13 | 14 | div#logo a { 15 | color: black; 16 | float: left; 17 | font-size: 18px; 18 | font-weight: bold; 19 | text-decoration: none; 20 | } 21 | 22 | div#header #navigation { 23 | text-align: right; 24 | } 25 | 26 | div#header #navigation a { 27 | color: black; 28 | font-size: 18px; 29 | font-weight: bold; 30 | margin-left: 12px; 31 | text-decoration: none; 32 | text-transform: uppercase; 33 | } 34 | 35 | div#footer { 36 | border-top: solid 2px black; 37 | color: #555; 38 | font-size: 12px; 39 | margin-top: 30px; 40 | padding: 12px 0px 12px 0px; 41 | text-align: right; 42 | } 43 | 44 | h1 { 45 | font-size: 24px; 46 | } 47 | 48 | h2 { 49 | font-size: 20px; 50 | } 51 | 52 | div.info { 53 | color: #555; 54 | font-size: 14px; 55 | font-style: italic; 56 | } 57 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright 2014 Yahoo! Inc. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the Yahoo! Inc. nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | Normalize.css License 33 | ===================== 34 | 35 | Copyright (c) Nicolas Gallagher and Jonathan Neal 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining a copy of 38 | this software and associated documentation files (the "Software"), to deal in 39 | the Software without restriction, including without limitation the rights to 40 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 41 | the Software, and to permit persons to whom the Software is furnished to do so, 42 | subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included in all 45 | copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 49 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 50 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 51 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 52 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/base-context-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v1.1.3 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */.pure article,.pure aside,.pure details,.pure figcaption,.pure figure,.pure footer,.pure header,.pure hgroup,.pure main,.pure nav,.pure section,.pure summary{display:block}.pure audio,.pure canvas,.pure video{display:inline-block;*display:inline;*zoom:1}.pure audio:not([controls]){display:none;height:0}.pure [hidden]{display:none}.pure{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.pure,.pure button,.pure input,.pure select,.pure textarea{font-family:sans-serif}.pure{margin:0}.pure a:focus{outline:thin dotted}.pure a:active,.pure a:hover{outline:0}.pure h1{font-size:2em;margin:.67em 0}.pure h2{font-size:1.5em;margin:.83em 0}.pure h3{font-size:1.17em;margin:1em 0}.pure h4{font-size:1em;margin:1.33em 0}.pure h5{font-size:.83em;margin:1.67em 0}.pure h6{font-size:.67em;margin:2.33em 0}.pure abbr[title]{border-bottom:1px dotted}.pure b,.pure strong{font-weight:700}.pure blockquote{margin:1em 40px}.pure dfn{font-style:italic}.pure hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}.pure mark{background:#ff0;color:#000}.pure p,.pure pre{margin:1em 0}.pure code,.pure kbd,.pure pre,.pure samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}.pure pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}.pure q{quotes:none}.pure q:before,.pure q:after{content:'';content:none}.pure small{font-size:80%}.pure sub,.pure sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.pure sup{top:-.5em}.pure sub{bottom:-.25em}.pure dl,.pure menu,.pure ol,.pure ul{margin:1em 0}.pure dd{margin:0 0 0 40px}.pure menu,.pure ol,.pure ul{padding:0 0 0 40px}.pure nav ul,.pure nav ol{list-style:none;list-style-image:none}.pure img{border:0;-ms-interpolation-mode:bicubic}.pure svg:not(:root){overflow:hidden}.pure figure{margin:0}.pure form{margin:0}.pure fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}.pure legend{border:0;padding:0;white-space:normal;*margin-left:-7px}.pure button,.pure input,.pure select,.pure textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.pure button,.pure input{line-height:normal}.pure button,.pure select{text-transform:none}.pure button,.pure input[type=button],.pure input[type=reset],.pure input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}.pure button[disabled],.pure input[disabled]{cursor:default}.pure input[type=checkbox],.pure input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}.pure input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}.pure input[type=search]::-webkit-search-cancel-button,.pure input[type=search]::-webkit-search-decoration{-webkit-appearance:none}.pure button::-moz-focus-inner,.pure input::-moz-focus-inner{border:0;padding:0}.pure textarea{overflow:auto;vertical-align:top}.pure table{border-collapse:collapse;border-spacing:0}.pure [hidden]{display:none!important}.pure .pure-img{max-width:100%;height:auto;display:block} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/base-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v1.1.3 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pure", 3 | "version": "0.5.0", 4 | "main": "pure.css", 5 | "devDependencies": { 6 | "normalize-css": "1.1.3" 7 | } 8 | } -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/buttons-core-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/buttons-core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-button { 8 | /* Structure */ 9 | display: inline-block; 10 | *display: inline; /*IE 6/7*/ 11 | zoom: 1; 12 | line-height: normal; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | text-align: center; 16 | cursor: pointer; 17 | -webkit-user-drag: none; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | /* Firefox: Get rid of the inner focus border */ 25 | .pure-button::-moz-focus-inner { 26 | padding: 0; 27 | border: 0; 28 | } 29 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/buttons-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/buttons.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-button { 8 | /* Structure */ 9 | display: inline-block; 10 | *display: inline; /*IE 6/7*/ 11 | zoom: 1; 12 | line-height: normal; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | text-align: center; 16 | cursor: pointer; 17 | -webkit-user-drag: none; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | /* Firefox: Get rid of the inner focus border */ 25 | .pure-button::-moz-focus-inner { 26 | padding: 0; 27 | border: 0; 28 | } 29 | 30 | /*csslint outline-none:false*/ 31 | 32 | .pure-button { 33 | font-family: inherit; 34 | font-size: 100%; 35 | *font-size: 90%; /*IE 6/7 - To reduce IE's oversized button text*/ 36 | *overflow: visible; /*IE 6/7 - Because of IE's overly large left/right padding on buttons */ 37 | padding: 0.5em 1em; 38 | color: #444; /* rgba not supported (IE 8) */ 39 | color: rgba(0, 0, 0, 0.80); /* rgba supported */ 40 | *color: #444; /* IE 6 & 7 */ 41 | border: 1px solid #999; /*IE 6/7/8*/ 42 | border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ 43 | background-color: #E6E6E6; 44 | text-decoration: none; 45 | border-radius: 2px; 46 | } 47 | 48 | .pure-button-hover, 49 | .pure-button:hover, 50 | .pure-button:focus { 51 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000',GradientType=0); 52 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10))); 53 | background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 54 | background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.10)); 55 | background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 56 | background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 57 | } 58 | .pure-button:focus { 59 | outline: 0; 60 | } 61 | .pure-button-active, 62 | .pure-button:active { 63 | box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; 64 | } 65 | 66 | .pure-button[disabled], 67 | .pure-button-disabled, 68 | .pure-button-disabled:hover, 69 | .pure-button-disabled:focus, 70 | .pure-button-disabled:active { 71 | border: none; 72 | background-image: none; 73 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 74 | filter: alpha(opacity=40); 75 | -khtml-opacity: 0.40; 76 | -moz-opacity: 0.40; 77 | opacity: 0.40; 78 | cursor: not-allowed; 79 | box-shadow: none; 80 | } 81 | 82 | .pure-button-hidden { 83 | display: none; 84 | } 85 | 86 | /* Firefox: Get rid of the inner focus border */ 87 | .pure-button::-moz-focus-inner{ 88 | padding: 0; 89 | border: 0; 90 | } 91 | 92 | .pure-button-primary, 93 | .pure-button-selected, 94 | a.pure-button-primary, 95 | a.pure-button-selected { 96 | background-color: rgb(0, 120, 231); 97 | color: #fff; 98 | } 99 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/grids-core-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/grids-core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/ 8 | 9 | .pure-g { 10 | letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ 11 | *letter-spacing: normal; /* reset IE < 8 */ 12 | *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ 13 | text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ 14 | 15 | /* 16 | Sets the font stack to fonts known to work properly with the above letter 17 | and word spacings. See: https://github.com/yui/pure/issues/41/ 18 | 19 | The following font stack makes Pure Grids work on all known environments. 20 | 21 | * FreeSans: Ships with many Linux distros, including Ubuntu 22 | 23 | * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and 24 | Arial to get picked up by the browser, even though neither is available 25 | in Chrome OS. 26 | 27 | * Droid Sans: Ships with all versions of Android. 28 | 29 | * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows. 30 | */ 31 | font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; 32 | 33 | /* 34 | Use flexbox when possible to avoid `letter-spacing` side-effects. 35 | 36 | NOTE: Firefox (as of 25) does not currently support flex-wrap, so the 37 | `-moz-` prefix version is omitted. 38 | */ 39 | 40 | display: -webkit-flex; 41 | -webkit-flex-flow: row wrap; 42 | 43 | /* IE10 uses display: flexbox */ 44 | display: -ms-flexbox; 45 | -ms-flex-flow: row wrap; 46 | } 47 | 48 | /* Opera as of 12 on Windows needs word-spacing. 49 | The ".opera-only" selector is used to prevent actual prefocus styling 50 | and is not required in markup. 51 | */ 52 | .opera-only :-o-prefocus, 53 | .pure-g { 54 | word-spacing: -0.43em; 55 | } 56 | 57 | .pure-u { 58 | display: inline-block; 59 | *display: inline; /* IE < 8: fake inline-block */ 60 | zoom: 1; 61 | letter-spacing: normal; 62 | word-spacing: normal; 63 | vertical-align: top; 64 | text-rendering: auto; 65 | } 66 | 67 | /* 68 | Resets the font family back to the OS/browser's default sans-serif font, 69 | this the same font stack that Normalize.css sets for the `body`. 70 | */ 71 | .pure-g [class *= "pure-u"] { 72 | font-family: sans-serif; 73 | } 74 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/grids-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/grids-units-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/menus-core-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/menus-core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*csslint adjoining-classes:false, outline-none:false*/ 8 | /*TODO: Remove this lint rule override after a refactor of this code.*/ 9 | 10 | .pure-menu ul { 11 | position: absolute; 12 | visibility: hidden; 13 | } 14 | 15 | .pure-menu.pure-menu-open { 16 | visibility: visible; 17 | z-index: 2; 18 | width: 100%; 19 | } 20 | 21 | .pure-menu ul { 22 | left: -10000px; 23 | list-style: none; 24 | margin: 0; 25 | padding: 0; 26 | top: -10000px; 27 | z-index: 1; 28 | } 29 | 30 | .pure-menu > ul { position: relative; } 31 | 32 | .pure-menu-open > ul { 33 | left: 0; 34 | top: 0; 35 | visibility: visible; 36 | } 37 | 38 | .pure-menu-open > ul:focus { 39 | outline: 0; 40 | } 41 | 42 | .pure-menu li { position: relative; } 43 | 44 | .pure-menu a, 45 | .pure-menu .pure-menu-heading { 46 | display: block; 47 | color: inherit; 48 | line-height: 1.5em; 49 | padding: 5px 20px; 50 | text-decoration: none; 51 | white-space: nowrap; 52 | } 53 | 54 | .pure-menu.pure-menu-horizontal > .pure-menu-heading { 55 | display: inline-block; 56 | *display: inline; 57 | zoom: 1; 58 | margin: 0; 59 | vertical-align: middle; 60 | } 61 | .pure-menu.pure-menu-horizontal > ul { 62 | display: inline-block; 63 | *display: inline; 64 | zoom: 1; 65 | vertical-align: middle; 66 | } 67 | 68 | .pure-menu li a { padding: 5px 20px; } 69 | 70 | .pure-menu-can-have-children > .pure-menu-label:after { 71 | content: '\25B8'; 72 | float: right; 73 | /* These specific fonts have the Unicode char we need. */ 74 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'DejaVu Sans', sans-serif; 75 | margin-right: -20px; 76 | margin-top: -1px; 77 | } 78 | 79 | .pure-menu-can-have-children > .pure-menu-label { 80 | padding-right: 30px; 81 | } 82 | 83 | .pure-menu-separator { 84 | background-color: #dfdfdf; 85 | display: block; 86 | height: 1px; 87 | font-size: 0; 88 | margin: 7px 2px; 89 | overflow: hidden; 90 | } 91 | 92 | .pure-menu-hidden { 93 | display: none; 94 | } 95 | 96 | /* FIXED MENU */ 97 | .pure-menu-fixed { 98 | position: fixed; 99 | top: 0; 100 | left: 0; 101 | width: 100%; 102 | } 103 | 104 | 105 | /* HORIZONTAL MENU CODE */ 106 | 107 | /* Initial menus should be inline-block so that they are horizontal */ 108 | .pure-menu-horizontal li { 109 | display: inline-block; 110 | *display: inline; 111 | zoom: 1; 112 | vertical-align: middle; 113 | } 114 | 115 | /* Submenus should still be display: block; */ 116 | .pure-menu-horizontal li li { 117 | display: block; 118 | } 119 | 120 | /* Content after should be down arrow */ 121 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label:after { 122 | content: "\25BE"; 123 | } 124 | /*Add extra padding to elements that have the arrow so that the hover looks nice */ 125 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label { 126 | padding-right: 30px; 127 | } 128 | 129 | /* Adjusting separator for vertical menus */ 130 | .pure-menu-horizontal li.pure-menu-separator { 131 | height: 50%; 132 | width: 1px; 133 | margin: 0 7px; 134 | } 135 | 136 | /* Submenus should be horizontal separator again */ 137 | .pure-menu-horizontal li li.pure-menu-separator { 138 | height: 1px; 139 | width: auto; 140 | margin: 7px 2px; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/menus-nr-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/menus-paginator-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/menus-paginator.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*csslint box-model:false*/ 8 | /*TODO: Remove this lint rule override after a refactor of this code.*/ 9 | 10 | 11 | .pure-paginator { 12 | 13 | /* `pure-g` Grid styles */ 14 | letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ 15 | *letter-spacing: normal; /* reset IE < 8 */ 16 | *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ 17 | text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ 18 | 19 | /* `pure-paginator` Specific styles */ 20 | list-style: none; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | .opera-only :-o-prefocus, 25 | .pure-paginator { 26 | word-spacing: -0.43em; 27 | } 28 | 29 | /* `pure-u` Grid styles */ 30 | .pure-paginator li { 31 | display: inline-block; 32 | *display: inline; /* IE < 8: fake inline-block */ 33 | zoom: 1; 34 | letter-spacing: normal; 35 | word-spacing: normal; 36 | vertical-align: top; 37 | text-rendering: auto; 38 | } 39 | 40 | 41 | .pure-paginator .pure-button { 42 | border-radius: 0; 43 | padding: 0.8em 1.4em; 44 | vertical-align: top; 45 | height: 1.1em; 46 | } 47 | .pure-paginator .pure-button:focus, 48 | .pure-paginator .pure-button:active { 49 | outline-style: none; 50 | } 51 | .pure-paginator .prev, 52 | .pure-paginator .next { 53 | color: #C0C1C3; 54 | text-shadow: 0 -1px 0 rgba(0,0,0, 0.45); 55 | } 56 | .pure-paginator .prev { 57 | border-radius: 2px 0 0 2px; 58 | } 59 | .pure-paginator .next { 60 | border-radius: 0 2px 2px 0; 61 | } 62 | -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/tables-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} -------------------------------------------------------------------------------- /barge-ui/css/pure-release-0.5.0/tables.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-table { 8 | /* Remove spacing between table cells (from Normalize.css) */ 9 | border-collapse: collapse; 10 | border-spacing: 0; 11 | empty-cells: show; 12 | border: 1px solid #cbcbcb; 13 | } 14 | 15 | .pure-table caption { 16 | color: #000; 17 | font: italic 85%/1 arial, sans-serif; 18 | padding: 1em 0; 19 | text-align: center; 20 | } 21 | 22 | .pure-table td, 23 | .pure-table th { 24 | border-left: 1px solid #cbcbcb;/* inner column border */ 25 | border-width: 0 0 0 1px; 26 | font-size: inherit; 27 | margin: 0; 28 | overflow: visible; /*to make ths where the title is really long work*/ 29 | padding: 0.5em 1em; /* cell padding */ 30 | } 31 | .pure-table td:first-child, 32 | .pure-table th:first-child { 33 | border-left-width: 0; 34 | } 35 | 36 | .pure-table thead { 37 | background: #e0e0e0; 38 | color: #000; 39 | text-align: left; 40 | vertical-align: bottom; 41 | } 42 | 43 | /* 44 | striping: 45 | even - #fff (white) 46 | odd - #f2f2f2 (light gray) 47 | */ 48 | .pure-table td { 49 | background-color: transparent; 50 | } 51 | .pure-table-odd td { 52 | background-color: #f2f2f2; 53 | } 54 | 55 | /* nth-child selector for modern browsers */ 56 | .pure-table-striped tr:nth-child(2n-1) td { 57 | background-color: #f2f2f2; 58 | } 59 | 60 | /* BORDERED TABLES */ 61 | .pure-table-bordered td { 62 | border-bottom: 1px solid #cbcbcb; 63 | } 64 | .pure-table-bordered tbody > tr:last-child td, 65 | .pure-table-horizontal tbody > tr:last-child td { 66 | border-bottom-width: 0; 67 | } 68 | 69 | 70 | /* HORIZONTAL BORDERED TABLES */ 71 | 72 | .pure-table-horizontal td, 73 | .pure-table-horizontal th { 74 | border-width: 0 0 1px 0; 75 | border-bottom: 1px solid #cbcbcb; 76 | } 77 | .pure-table-horizontal tbody > tr:last-child td { 78 | border-bottom-width: 0; 79 | } 80 | -------------------------------------------------------------------------------- /barge-ui/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 80%; 3 | } 4 | 5 | .node { 6 | margin: 1em; 7 | padding: 1em; 8 | border: thin solid #000; 9 | } 10 | 11 | .header { 12 | background-color: #000; 13 | color: white; 14 | } 15 | 16 | .pure-menu { 17 | font-size: 200%; 18 | font-weight: bold; 19 | } 20 | 21 | .pure-table { 22 | margin-top: 1em; 23 | } 24 | 25 | td.appendEntries { 26 | background-color: #FEE0C6; 27 | } 28 | 29 | td.requestVote { 30 | background-color: #FDF2EE; 31 | } 32 | 33 | td.commit { 34 | background-color:#EEF3E2; 35 | } 36 | 37 | td.init { 38 | background-color: #EBECE4; 39 | } 40 | 41 | td.stateChange { 42 | background-color: #FEFFF1; 43 | } 44 | 45 | td.invalidTransition { 46 | background-color: #8B8378; 47 | color: white; 48 | } 49 | 50 | td.stopping { 51 | background-color: #8B0000; 52 | color: lightgray; 53 | } 54 | -------------------------------------------------------------------------------- /barge-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Barge: A Raft Implementation in Java 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | Barge 13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /barge-ui/project.clj: -------------------------------------------------------------------------------- 1 | (defproject barge-ui "0.1.0-SNAPSHOT" 2 | :description "A minimal interface to barge REST API for demo purpose" 3 | :url "http://github.com/mgodave/barge" 4 | 5 | :jvm-opts ^:replace ["-Xmx1g" "-server"] 6 | 7 | :dependencies [[org.clojure/clojure "1.5.1"] 8 | [org.clojure/clojurescript "0.0-2173"] 9 | [org.clojure/core.async "0.1.267.0-0d7780-alpha"] 10 | [org.clojure/tools.nrepl "0.2.3"] 11 | [om "0.6.4"]] 12 | 13 | :plugins [[lein-cljsbuild "1.0.2"]] 14 | 15 | :source-paths ["src"] 16 | 17 | :cljsbuild { 18 | :builds [{:id "dev" 19 | :source-paths ["src"] 20 | :compiler { 21 | :output-to "out/app.js" 22 | :output-dir "out" 23 | :pretty-print true 24 | :optimizations :none 25 | :source-map true}} 26 | {:id "release" 27 | :source-paths ["src"] 28 | :compiler { 29 | :output-to "app.js" 30 | :optimizations :advanced 31 | :elide-asserts true 32 | :pretty-print false 33 | :output-wrapper false 34 | :preamble ["react.min.js"] 35 | :externs ["react/externs/react.js"]}}]}) 36 | -------------------------------------------------------------------------------- /barge-ui/src/barge/messaging.cljs: -------------------------------------------------------------------------------- 1 | (ns barge.messaging 2 | "Provides messaging logic between UI and WS server." 3 | 4 | (:import [goog.net WebSocket] 5 | goog.net.WebSocket.EventType 6 | [goog.events EventHandler])) 7 | 8 | 9 | (defn- parse-msg 10 | "parse message into a clojure object." 11 | [msg] 12 | (cond 13 | (string? msg) 14 | (js->clj (JSON/parse msg) :keywordize-keys true) 15 | 16 | true 17 | msg)) 18 | 19 | (defn- coalesce 20 | "prepend msg to msgs, coalescing msgs which differ only by their timestamps" 21 | [msg msgs] 22 | (cond 23 | (nil? msgs) 24 | [msg] 25 | 26 | true 27 | (let [m1 (assoc-in msg [:timestamp] nil) 28 | m2 (assoc-in (first msgs) [:timestamp] nil)] 29 | (if (= m1 m2) 30 | (cons msg (rest msgs)) 31 | (cons msg msgs)) 32 | ) 33 | ) 34 | ) 35 | 36 | (defn- update-msgs [state node msg] 37 | "update the messages list for given node, appending a new message 38 | 39 | state is updated asynchronously" 40 | (swap! state (fn [st] 41 | (update-in st [(keyword node) :msgs] (partial coalesce (parse-msg msg)))))) 42 | ;; message handlers 43 | (defn- on-msg [state node e] 44 | "Handles messages from the server, that is notifications regarding the state of some node" 45 | (let [msg (.-message e) 46 | ;; msg is a Blob which should be decoded by a js/FileReader 47 | ;; see https://developer.mozilla.org/en-US/docs/Web/API/Blob 48 | reader (js/FileReader.)] 49 | ;; callback updates messages with whatever text is contained in the blob 50 | (.addEventListener reader "loadend" #(update-msgs state node (.-result reader))) 51 | ;; text is assumed to be encoded in UTF-8 which BTW is the default 52 | (.readAsText reader msg "UTF-8")) 53 | ) 54 | 55 | ;; TODO better notification of node's state 56 | ;; we want to add a notification area within each node that displays until dismissed 57 | ;; notification messages 58 | (defn- on-open [e] 59 | (.log js/console (str "open " e))) 60 | 61 | (defn- on-close [e] 62 | (.log js/console (str "close " e))) 63 | 64 | (defn- on-error [e] 65 | (.log js/console (str "error " (.-type e)))) 66 | 67 | (defn connect [st node node-state] 68 | "Connect to given node id and updates state accordingly 69 | 70 | * st: current state 71 | * node: identifier of node to connect to 72 | * node-state: a *cursor*, eg. a reference to some state that might be updated 73 | by future messages. Should probably be replaced by some async channel for better encapsulation" 74 | (let [ws (WebSocket.) 75 | eh (EventHandler.) 76 | uri (:uri ((keyword node) st))] 77 | 78 | (.listen eh ws EventType.MESSAGE (partial on-msg node-state node)) 79 | (.listen eh ws EventType.OPENED on-open) 80 | (.listen eh ws EventType.CLOSED on-close) 81 | (.listen eh ws EventType.ERROR on-error) 82 | (.open ws uri) 83 | (assoc-in st [(keyword node) :ws] ws))) 84 | 85 | (defn disconnect [st node] 86 | "disconnect node and updates state accordingly" 87 | (if-let [ws (:ws ((keyword node) st))] 88 | (do (.close ws) 89 | (assoc-in st [(keyword node) :ws] nil)))) 90 | 91 | -------------------------------------------------------------------------------- /travis-settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | sonatype-nexus-snapshots 9 | ${env.CI_DEPLOY_USERNAME} 10 | ${env.CI_DEPLOY_PASSWORD} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | false 21 | 22 | 23 | true 24 | 25 | sonatype-nexus-snapshots 26 | Sonatype Nexus Snapshots 27 | https://oss.sonatype.org/content/repositories/snapshots/ 28 | 29 | 30 | 31 | 32 | 33 | 34 | sonatype-nexus-snapshots 35 | 36 | 37 | --------------------------------------------------------------------------------