├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── bin ├── ci-deploy-snapshot.sh ├── ci-push-javadoc.sh ├── push-javadoc.sh └── settings.xml ├── client ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── atomix │ │ └── copycat │ │ └── client │ │ ├── ConnectionStrategies.java │ │ ├── ConnectionStrategy.java │ │ ├── CopycatClient.java │ │ ├── DefaultCopycatClient.java │ │ ├── RecoveryStrategies.java │ │ ├── RecoveryStrategy.java │ │ ├── ServerSelectionStrategies.java │ │ ├── ServerSelectionStrategy.java │ │ ├── package-info.java │ │ ├── session │ │ ├── ClientSequencer.java │ │ ├── ClientSession.java │ │ ├── ClientSessionListener.java │ │ ├── ClientSessionManager.java │ │ ├── ClientSessionState.java │ │ ├── ClientSessionSubmitter.java │ │ └── package-info.java │ │ └── util │ │ ├── AddressSelector.java │ │ ├── ClientConnection.java │ │ └── OrderedCompletableFuture.java │ └── test │ ├── java │ └── io │ │ └── atomix │ │ └── copycat │ │ └── client │ │ ├── ConnectionStrategiesTest.java │ │ ├── DefaultCopycatClientTest.java │ │ ├── RecoveryStrategiesTest.java │ │ ├── ServerSelectionStrategiesTest.java │ │ ├── session │ │ ├── ClientSequencerTest.java │ │ ├── ClientSessionManagerTest.java │ │ ├── ClientSessionStateTest.java │ │ └── ClientSessionSubmitterTest.java │ │ └── util │ │ ├── AddressSelectorTest.java │ │ └── OrderedCompletableFutureTest.java │ └── resources │ └── logback.xml ├── examples ├── pom.xml ├── value-client │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── io │ │ │ └── atomix │ │ │ └── copycat │ │ │ └── examples │ │ │ └── ValueClientExample.java │ │ └── resources │ │ └── logback.xml └── value-state-machine │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── io │ │ └── atomix │ │ └── copycat │ │ └── examples │ │ ├── DeleteCommand.java │ │ ├── GetQuery.java │ │ ├── SetCommand.java │ │ ├── ValueStateMachine.java │ │ └── ValueStateMachineExample.java │ └── resources │ └── logback.xml ├── pom.xml ├── protocol ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── atomix │ │ │ └── copycat │ │ │ ├── Command.java │ │ │ ├── NoOpCommand.java │ │ │ ├── Operation.java │ │ │ ├── Query.java │ │ │ ├── error │ │ │ ├── ApplicationException.java │ │ │ ├── CommandException.java │ │ │ ├── ConfigurationException.java │ │ │ ├── CopycatError.java │ │ │ ├── CopycatException.java │ │ │ ├── IllegalMemberStateException.java │ │ │ ├── InternalException.java │ │ │ ├── NoLeaderException.java │ │ │ ├── OperationException.java │ │ │ ├── QueryException.java │ │ │ ├── UnknownSessionException.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── protocol │ │ │ ├── AbstractRequest.java │ │ │ ├── AbstractResponse.java │ │ │ ├── ClientRequestTypeResolver.java │ │ │ ├── ClientResponseTypeResolver.java │ │ │ ├── CommandRequest.java │ │ │ ├── CommandResponse.java │ │ │ ├── ConnectRequest.java │ │ │ ├── ConnectResponse.java │ │ │ ├── KeepAliveRequest.java │ │ │ ├── KeepAliveResponse.java │ │ │ ├── OperationRequest.java │ │ │ ├── OperationResponse.java │ │ │ ├── PublishRequest.java │ │ │ ├── QueryRequest.java │ │ │ ├── QueryResponse.java │ │ │ ├── RegisterRequest.java │ │ │ ├── RegisterResponse.java │ │ │ ├── Request.java │ │ │ ├── ResetRequest.java │ │ │ ├── Response.java │ │ │ ├── SessionRequest.java │ │ │ ├── SessionResponse.java │ │ │ ├── UnregisterRequest.java │ │ │ ├── UnregisterResponse.java │ │ │ └── package-info.java │ │ │ ├── session │ │ │ ├── ClosedSessionException.java │ │ │ ├── Event.java │ │ │ ├── Session.java │ │ │ └── package-info.java │ │ │ └── util │ │ │ └── ProtocolSerialization.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.atomix.catalyst.serializer.CatalystSerializable │ └── test │ └── java │ └── io │ └── atomix │ └── copycat │ └── error │ └── CopycatErrorTest.java ├── server ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── atomix │ │ └── copycat │ │ └── server │ │ ├── Commit.java │ │ ├── CopycatServer.java │ │ ├── Snapshottable.java │ │ ├── StateMachine.java │ │ ├── StateMachineContext.java │ │ ├── StateMachineExecutor.java │ │ ├── cluster │ │ ├── Cluster.java │ │ ├── Member.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── protocol │ │ ├── AppendRequest.java │ │ ├── AppendResponse.java │ │ ├── ConfigurationRequest.java │ │ ├── ConfigurationResponse.java │ │ ├── ConfigureRequest.java │ │ ├── ConfigureResponse.java │ │ ├── InstallRequest.java │ │ ├── InstallResponse.java │ │ ├── JoinRequest.java │ │ ├── JoinResponse.java │ │ ├── LeaveRequest.java │ │ ├── LeaveResponse.java │ │ ├── PollRequest.java │ │ ├── PollResponse.java │ │ ├── ReconfigureRequest.java │ │ ├── ReconfigureResponse.java │ │ ├── VoteRequest.java │ │ ├── VoteResponse.java │ │ └── package-info.java │ │ ├── session │ │ ├── ServerSession.java │ │ ├── SessionListener.java │ │ ├── Sessions.java │ │ └── package-info.java │ │ ├── state │ │ ├── AbstractAppender.java │ │ ├── AbstractState.java │ │ ├── ActiveState.java │ │ ├── CandidateState.java │ │ ├── ClusterState.java │ │ ├── ConnectionManager.java │ │ ├── FollowerAppender.java │ │ ├── FollowerState.java │ │ ├── InactiveState.java │ │ ├── LeaderAppender.java │ │ ├── LeaderState.java │ │ ├── MemberState.java │ │ ├── PassiveState.java │ │ ├── ReserveState.java │ │ ├── ServerClock.java │ │ ├── ServerCommit.java │ │ ├── ServerCommitPool.java │ │ ├── ServerContext.java │ │ ├── ServerMember.java │ │ ├── ServerSessionContext.java │ │ ├── ServerSessionManager.java │ │ ├── ServerState.java │ │ ├── ServerStateMachine.java │ │ ├── ServerStateMachineContext.java │ │ ├── ServerStateMachineExecutor.java │ │ └── package-info.java │ │ ├── storage │ │ ├── DescriptorException.java │ │ ├── Log.java │ │ ├── Segment.java │ │ ├── SegmentDescriptor.java │ │ ├── SegmentFile.java │ │ ├── SegmentManager.java │ │ ├── Storage.java │ │ ├── StorageCleaner.java │ │ ├── StorageException.java │ │ ├── StorageLevel.java │ │ ├── compaction │ │ │ ├── Compaction.java │ │ │ ├── CompactionManager.java │ │ │ ├── CompactionTask.java │ │ │ ├── Compactor.java │ │ │ ├── MajorCompactionManager.java │ │ │ ├── MajorCompactionTask.java │ │ │ ├── MinorCompactionManager.java │ │ │ ├── MinorCompactionTask.java │ │ │ └── package-info.java │ │ ├── entry │ │ │ ├── CommandEntry.java │ │ │ ├── ConfigurationEntry.java │ │ │ ├── Entry.java │ │ │ ├── InitializeEntry.java │ │ │ ├── KeepAliveEntry.java │ │ │ ├── OperationEntry.java │ │ │ ├── QueryEntry.java │ │ │ ├── RegisterEntry.java │ │ │ ├── SessionEntry.java │ │ │ ├── TimestampedEntry.java │ │ │ ├── TypedEntryPool.java │ │ │ ├── UnregisterEntry.java │ │ │ └── package-info.java │ │ ├── index │ │ │ ├── DelegatingOffsetIndex.java │ │ │ ├── OffsetIndex.java │ │ │ ├── SearchableOffsetIndex.java │ │ │ └── SequentialOffsetIndex.java │ │ ├── package-info.java │ │ ├── snapshot │ │ │ ├── FileSnapshot.java │ │ │ ├── MemorySnapshot.java │ │ │ ├── Snapshot.java │ │ │ ├── SnapshotDescriptor.java │ │ │ ├── SnapshotFile.java │ │ │ ├── SnapshotReader.java │ │ │ ├── SnapshotStore.java │ │ │ ├── SnapshotWriter.java │ │ │ └── package-info.java │ │ ├── system │ │ │ ├── Configuration.java │ │ │ ├── MetaStore.java │ │ │ └── package-info.java │ │ └── util │ │ │ ├── EntryBuffer.java │ │ │ ├── OffsetPredicate.java │ │ │ ├── StorageSerialization.java │ │ │ └── TermIndex.java │ │ └── util │ │ ├── Quorum.java │ │ └── ServerSerialization.java │ └── test │ ├── java │ └── io │ │ └── atomix │ │ └── copycat │ │ └── server │ │ ├── TestStateMachine.java │ │ ├── Testing.java │ │ ├── state │ │ ├── AbstractStateTest.java │ │ ├── ActiveStateTest.java │ │ ├── CandidateStateTest.java │ │ ├── FollowerStateTest.java │ │ ├── LeaderStateTest.java │ │ ├── MemberTest.java │ │ ├── PassiveStateTest.java │ │ ├── ServerContextTest.java │ │ ├── ServerSessionTest.java │ │ └── ServerStateMachineTest.java │ │ ├── storage │ │ ├── AbstractLogTest.java │ │ ├── AbstractSnapshotStoreTest.java │ │ ├── FileLogTest.java │ │ ├── FileSnapshotStoreTest.java │ │ ├── LogTest.java │ │ ├── MajorCompactionTest.java │ │ ├── MappedLogTest.java │ │ ├── MemoryLogTest.java │ │ ├── MemorySnapshotStoreTest.java │ │ ├── MetaStoreTest.java │ │ ├── MinorCompactionTest.java │ │ ├── OffsetIndexTest.java │ │ ├── OffsetPredicateTest.java │ │ ├── SegmentDescriptorTest.java │ │ ├── SegmentManagerTest.java │ │ └── TestEntry.java │ │ └── util │ │ └── QuorumTest.java │ └── resources │ ├── META-INF │ └── services │ │ └── io.atomix.catalyst.serializer.CatalystSerializable │ └── logback.xml └── test ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── atomix │ │ └── copycat │ │ └── test │ │ ├── FuzzTest.java │ │ └── PerformanceTest.java └── resources │ └── logback.xml └── test ├── java └── io │ └── atomix │ └── copycat │ └── test │ └── ClusterTest.java └── resources └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | test-output/ 3 | .settings/ 4 | .classpath/ 5 | *.project 6 | .idea 7 | *.classpath 8 | *.iml 9 | .DS_Store 10 | pom.xml.tag 11 | pom.xml.releaseBackup 12 | pom.xml.versionsBackup 13 | pom.xml.next 14 | release.properties 15 | dependency-reduced-pom.xml 16 | buildNumber.properties -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | os: linux 4 | dist: trusty 5 | 6 | jdk: 7 | - oraclejdk8 8 | - openjdk8 9 | 10 | notifications: 11 | email: false 12 | 13 | branches: 14 | only: 15 | - master 16 | 17 | script: 18 | - mvn test -Droot.logging.level=INFO 19 | 20 | after_success: 21 | - mvn clean test jacoco:report coveralls:report -Droot.logging.level=INFO 22 | - bin/ci-push-javadoc.sh 23 | - bin/ci-deploy-snapshot.sh 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copycat 2 | 3 | [![Build Status](https://travis-ci.org/atomix/copycat.svg)](https://travis-ci.org/atomix/copycat) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.atomix.copycat/copycat-server/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.atomix.copycat%22) 5 | [![Gitter](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/atomix/atomix) 6 | 7 | ### Copycat has moved! 8 | 9 | Copycat 2.x is now [atomix-raft](https://github.com/atomix/atomix/blob/master/protocols/raft/src/main/java/io/atomix/protocols/raft) 10 | and includes a variety of improvements to Copycat 1.x: 11 | * Multiple state machines per cluster 12 | * Multiple sessions per client 13 | * Index-free memory mapped log 14 | * Per-state-machine snapshots 15 | * Framework agnostic serialization 16 | * Partitioning 17 | * etc 18 | 19 | This repository is no longer officially maintained. 20 | -------------------------------------------------------------------------------- /bin/ci-deploy-snapshot.sh: -------------------------------------------------------------------------------- 1 | # From https://coderwall.com/p/9b_lfq 2 | 3 | REPO="atomix/copycat" 4 | 5 | if [ "$TRAVIS_REPO_SLUG" == "$REPO" ] && \ 6 | [ "$TRAVIS_JDK_VERSION" == "oraclejdk8" ] && \ 7 | [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ 8 | [ "$TRAVIS_BRANCH" == "master" ]; then 9 | echo -e "Publishing maven snapshot...\n" 10 | 11 | mvn clean source:jar deploy --settings="bin/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true 12 | 13 | echo -e "Published maven snapshot" 14 | fi -------------------------------------------------------------------------------- /bin/ci-push-javadoc.sh: -------------------------------------------------------------------------------- 1 | # Called by Travis CI to push latest javadoc 2 | # From http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ 3 | 4 | PROJECT=copycat 5 | 6 | if [ "$TRAVIS_REPO_SLUG" == "atomix/$PROJECT" ] && \ 7 | [ "$TRAVIS_JDK_VERSION" == "oraclejdk8" ] && \ 8 | [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ 9 | [ "$TRAVIS_BRANCH" == "master" ]; then 10 | echo -e "Publishing Javadoc...\n" 11 | 12 | mvn javadoc:javadoc -Djv=latest 13 | TARGET="$(pwd)/target" 14 | 15 | cd $HOME 16 | git clone --quiet https://${GH_TOKEN}@github.com/atomix/atomix.github.io gh-pages > /dev/null 17 | 18 | cd gh-pages 19 | git config --global user.email "travis@travis-ci.org" 20 | git config --global user.name "travis-ci" 21 | git rm -rf $PROJECT/api/latest 22 | mkdir -p $PROJECT/api/latest 23 | mv -v $TARGET/site/apidocs/* $PROJECT/api/latest 24 | git add -A -f $PROJECT/api/latest 25 | git commit -m "Travis generated Javadoc for $PROJECT build $TRAVIS_BUILD_NUMBER" 26 | git push -fq origin > /dev/null 27 | 28 | echo -e "Published Javadoc to atomix.github.io.\n" 29 | fi 30 | -------------------------------------------------------------------------------- /bin/push-javadoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Pushes javadocs for an given release version 3 | # Run from top level dir 4 | 5 | PROJECT=copycat 6 | 7 | echo "Enter the API version to generate docs for: " 8 | read apiVersion 9 | 10 | mvn javadoc:javadoc -Djv=$apiVersion 11 | rm -rf target/docs 12 | git clone git@github.com:atomix/atomix.github.io.git target/docs > /dev/null 13 | cd target/docs 14 | git rm -rf $PROJECT/api/$apiVersion 15 | mkdir -p $PROJECT/api/$apiVersion 16 | mv -v ../site/apidocs/* $PROJECT/api/$apiVersion 17 | git add -A -f $PROJECT/api/$apiVersion 18 | git commit -m "Updated JavaDocs for $apiVersion" 19 | git push -fq origin master > /dev/null 20 | 21 | echo "Published $apiVersion Javadoc to atomix.github.io.\n" -------------------------------------------------------------------------------- /bin/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-snapshots 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 4.0.0 18 | 19 | 20 | io.atomix.copycat 21 | copycat-parent 22 | 1.2.9-SNAPSHOT 23 | 24 | 25 | bundle 26 | copycat-client 27 | Copycat Client 28 | 29 | 30 | 31 | io.atomix.copycat 32 | copycat-protocol 33 | ${project.version} 34 | 35 | 36 | org.mockito 37 | mockito-all 38 | 1.10.19 39 | test 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client/src/main/java/io/atomix/copycat/client/RecoveryStrategies.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.client; 17 | 18 | /** 19 | * Strategies for recovering lost client sessions. 20 | *

21 | * Client recovery strategies are responsible for recovering a crashed client. When clients fail to contact 22 | * a server for more than their session timeout, the client's session must be closed as linearizability is 23 | * lost. The recovery strategy has the opportunity to recover the crashed client gracefully. 24 | * 25 | * @author 21 | * Client recovery strategies are responsible for recovering a crashed client. When a client is unable 22 | * to communicate with the cluster for some time period, the cluster may expire the client's session. 23 | * In the event that a client reconnects and discovers its session is expired, the client's configured 24 | * recovery strategy will be queried to determine how to handle the failure. Typically, recovery strategies 25 | * can either {@link CopycatClient#recover() recover} or {@link CopycatClient#close() close} the client. 26 | * 27 | * @author 25 | * Selection strategies prioritize communication with certain servers over others. When the client 26 | * loses its connection or cluster membership changes, the client will request a list of servers to 27 | * which the client can connect. The address list should be prioritized. 28 | * 29 | * @author 20 | * The client is a fully featured client that facilitates submitting {@link io.atomix.copycat.Command commands} 21 | * and {@link io.atomix.copycat.Query queries} to a Copycat cluster. Clients communicate with the cluster within 22 | * the context of a session. To create a client and connect to the cluster, use the {@link io.atomix.copycat.client.CopycatClient.Builder}. 23 | *

24 |  *   {@code
25 |  *   CopycatClient client = CopycatClient.builder(servers)
26 |  *     .withTransport(new NettyTransport())
27 |  *     .build();
28 |  *   client.open().join();
29 |  *   }
30 |  * 
31 | * Clients are designed to communicate with any server in the cluster in a transparent manner. See the 32 | * {@link io.atomix.copycat.client.CopycatClient} documentation for more information. 33 | * 34 | * @author Jordan Halterman 35 | */ 36 | package io.atomix.copycat.client; 37 | -------------------------------------------------------------------------------- /client/src/main/java/io/atomix/copycat/client/session/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 | /** 18 | * Facilitates communication with the Copycat cluster within the context of a session. 19 | *

20 | * Clients communicate with the Copycat cluster within the context of a session. Sessions are used to 21 | * achieve linearizable semantics and first-in-first-out ordering of client operations by coordinating 22 | * {@link io.atomix.copycat.Command commands} and {@link io.atomix.copycat.Query queries} submitted by 23 | * the client to the cluster. Additionally, sessions facilitate listening for event notifications from 24 | * the cluster. When state changes occur in the server-side replicated state machine, state machines 25 | * can publish messages notifying the client of events. The session aids in guaranteeing sequential 26 | * and linearizable consistency for server-to-client communication. 27 | * 28 | * @author Jordan Halterman 29 | */ 30 | package io.atomix.copycat.client.session; 31 | -------------------------------------------------------------------------------- /client/src/test/java/io/atomix/copycat/client/RecoveryStrategiesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.client; 17 | 18 | import org.testng.annotations.Test; 19 | 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.verify; 22 | 23 | /** 24 | * Session recovery strategies test. 25 | * 26 | * @author assertEquals(3, order.incrementAndGet())); 31 | future.thenAccept(r -> { 32 | assertEquals(5, order.incrementAndGet()); 33 | assertEquals(r, "foo"); 34 | }); 35 | future.thenApply(r -> { 36 | assertEquals(6, order.incrementAndGet()); 37 | assertEquals(r, "foo"); 38 | return "bar"; 39 | }); 40 | future.whenComplete((r, e) -> { 41 | assertEquals(7, order.incrementAndGet()); 42 | assertEquals(r, "foo"); 43 | }); 44 | future.complete("foo"); 45 | } 46 | 47 | /** 48 | * Tests ordered failure of future callbacks. 49 | */ 50 | public void testOrderedFailure() throws Throwable { 51 | CompletableFuture future = new OrderedCompletableFuture<>(); 52 | AtomicInteger order = new AtomicInteger(); 53 | future.whenComplete((r, e) -> assertEquals(1, order.incrementAndGet())); 54 | future.whenComplete((r, e) -> assertEquals(2, order.incrementAndGet())); 55 | future.handle((r, e) -> { 56 | assertEquals(3, order.incrementAndGet()); 57 | return "bar"; 58 | }); 59 | future.thenRun(() -> fail()); 60 | future.thenAccept(r -> fail()); 61 | future.exceptionally(e -> { 62 | assertEquals(3, order.incrementAndGet()); 63 | return "bar"; 64 | }); 65 | future.completeExceptionally(new RuntimeException("foo")); 66 | } 67 | 68 | /** 69 | * Tests calling callbacks that are added after completion. 70 | */ 71 | public void testAfterComplete() throws Throwable { 72 | CompletableFuture future = new OrderedCompletableFuture<>(); 73 | future.whenComplete((result, error) -> assertEquals(result, "foo")); 74 | future.complete("foo"); 75 | AtomicInteger count = new AtomicInteger(); 76 | future.whenComplete((result, error) -> { 77 | assertEquals(result, "foo"); 78 | assertEquals(count.incrementAndGet(), 1); 79 | }); 80 | future.thenAccept(result -> { 81 | assertEquals(result, "foo"); 82 | assertEquals(count.incrementAndGet(), 2); 83 | }); 84 | assertEquals(count.get(), 2); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /client/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 4.0.0 18 | 19 | 20 | io.atomix.copycat 21 | copycat-parent 22 | 1.2.9-SNAPSHOT 23 | 24 | 25 | copycat-examples-parent 26 | pom 27 | Copycat Examples Parent 28 | Example projects for Copycat. 29 | http://github.com/atomix/copycat 30 | 2013 31 | 32 | 33 | 1.1.2 34 | 35 | 36 | 37 | value-state-machine 38 | value-client 39 | 40 | 41 | 42 | 43 | ch.qos.logback 44 | logback-classic 45 | ${logback.version} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-deploy-plugin 55 | 2.8.2 56 | 57 | true 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-site-plugin 65 | 3.4 66 | 67 | true 68 | true 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/value-client/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/value-state-machine/src/main/java/io/atomix/copycat/examples/DeleteCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.examples; 17 | 18 | import io.atomix.copycat.Command; 19 | 20 | /** 21 | * Value delete command. 22 | * 23 | * @author 31 | *

  • path - the sub-directory to store log files in
  • 32 | *
  • host:port pairs - the host address and port of cluster members
  • 33 | * 34 | *

    Example cluster arguments:

    logs 10.0.1.10:5000 10.0.1.11:5001 10.0.1.12:5002
    35 | *

    Example single node arguments:

    logs localhost:5000
    36 | * 37 | * @author
    members = new ArrayList<>(); 54 | for (int i = 1; i < args.length; i++) { 55 | String[] parts = args[i].split(":"); 56 | members.add(new Address(parts[0], Integer.valueOf(parts[1]))); 57 | } 58 | 59 | CopycatServer server = CopycatServer.builder(address) 60 | .withStateMachine(ValueStateMachine::new) 61 | .withTransport(new NettyTransport()) 62 | .withStorage(Storage.builder() 63 | .withDirectory(args[0]) 64 | .withMaxSegmentSize(1024 * 1024 * 32) 65 | .withMinorCompactionInterval(Duration.ofMinutes(1)) 66 | .withMajorCompactionInterval(Duration.ofMinutes(15)) 67 | .build()) 68 | .build(); 69 | 70 | server.serializer().register(SetCommand.class, 1); 71 | server.serializer().register(GetQuery.class, 2); 72 | server.serializer().register(DeleteCommand.class, 3); 73 | 74 | server.bootstrap(members).join(); 75 | 76 | while (server.isRunning()) { 77 | Thread.sleep(1000); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /examples/value-state-machine/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /protocol/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 4.0.0 18 | 19 | 20 | io.atomix.copycat 21 | copycat-parent 22 | 1.2.9-SNAPSHOT 23 | 24 | 25 | bundle 26 | copycat-protocol 27 | Copycat Protocol 28 | 29 | 30 | 31 | io.atomix.catalyst 32 | catalyst-transport 33 | ${catalyst.version} 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/NoOpCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.CatalystSerializable; 21 | import io.atomix.catalyst.serializer.Serializer; 22 | 23 | /** 24 | * Special placeholder command representing a client operation that has no effect on the state machine. 25 | *

    26 | * No-op commands are submitted by clients to the cluster to complete missing command sequence numbers. 27 | * Copycat clusters require that clients submit commands in sequential order and use a client-provided 28 | * sequence number to ensure FIFO ordering of operations submitted to the cluster. In the event that a 29 | * command fails to be committed to the cluster, a client can resubmit a no-op command to ensure command 30 | * sequence numbers continue to progress. 31 | * 32 | * @author 25 | * This is a base interface for operations on the Raft cluster state. Operations are submitted to Raft clusters 26 | * by clients via a {@link Session}. All operations are sent over the network 27 | * and thus must be serializable by the client and by all servers in the cluster. By default, Java serialization 28 | * is used. However, it is recommended that operations implement {@link io.atomix.catalyst.serializer.CatalystSerializable} 29 | * or register a {@link io.atomix.catalyst.serializer.TypeSerializer} for better performance. 30 | * 31 | * @see Command 32 | * @see Query 33 | * 34 | * @param operation result type 35 | * @author Jordan Halterman 36 | */ 37 | public interface Operation extends Serializable { 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/ApplicationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that an exception occurred in the user state machine. 20 | *

    21 | * Application exceptions are thrown when an exception occurs within a user-provided state machine. 22 | * 23 | * @author Jordan Halterman 24 | */ 25 | public class ApplicationException extends CopycatException { 26 | private static final CopycatError.Type TYPE = CopycatError.Type.APPLICATION_ERROR; 27 | 28 | public ApplicationException(String message, Object... args) { 29 | super(TYPE, message, args); 30 | } 31 | 32 | public ApplicationException(Throwable cause, String message, Object... args) { 33 | super(TYPE, cause, message, args); 34 | } 35 | 36 | public ApplicationException(Throwable cause) { 37 | super(TYPE, cause); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/CommandException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that an error occurred while committing a write command. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class CommandException extends OperationException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.COMMAND_ERROR; 25 | 26 | public CommandException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public CommandException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public CommandException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that an error occurred while committing a write command. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class ConfigurationException extends OperationException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.CONFIGURATION_ERROR; 25 | 26 | public ConfigurationException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public ConfigurationException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public ConfigurationException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/CopycatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Base Copycat protocol exception. 20 | *

    21 | * This is the base exception type for all Copycat protocol exceptions. Protocol exceptions must be 22 | * associated with a {@link CopycatError.Type} which is used for more efficient serialization. 23 | * 24 | * @author Jordan Halterman 25 | */ 26 | public abstract class CopycatException extends RuntimeException { 27 | private final CopycatError.Type type; 28 | 29 | protected CopycatException(CopycatError.Type type, String message, Object... args) { 30 | super(String.format(message, args)); 31 | if (type == null) 32 | throw new NullPointerException("type cannot be null"); 33 | this.type = type; 34 | } 35 | 36 | protected CopycatException(CopycatError.Type type, Throwable cause, String message, Object... args) { 37 | super(String.format(message, args), cause); 38 | if (type == null) 39 | throw new NullPointerException("type cannot be null"); 40 | this.type = type; 41 | } 42 | 43 | protected CopycatException(CopycatError.Type type, Throwable cause) { 44 | super(cause); 45 | if (type == null) 46 | throw new NullPointerException("type cannot be null"); 47 | this.type = type; 48 | } 49 | 50 | /** 51 | * Returns the exception type. 52 | * 53 | * @return The exception type. 54 | */ 55 | public CopycatError.Type getType() { 56 | return type; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/IllegalMemberStateException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that a server received a request for which it was not in the appropriate state to handle. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class IllegalMemberStateException extends CopycatException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.ILLEGAL_MEMBER_STATE_ERROR; 25 | 26 | public IllegalMemberStateException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public IllegalMemberStateException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public IllegalMemberStateException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/InternalException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Catch-all exception for unknown Raft protocol errors. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class InternalException extends CopycatException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.INTERNAL_ERROR; 25 | 26 | public InternalException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public InternalException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public InternalException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/NoLeaderException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that the server that received a request is not the leader and does not know of a leader. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class NoLeaderException extends CopycatException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.NO_LEADER_ERROR; 25 | 26 | public NoLeaderException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public NoLeaderException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public NoLeaderException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/OperationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Base class for operation exceptions {@link CommandException} and {@link QueryException}. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class QueryException extends OperationException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.QUERY_ERROR; 25 | 26 | public QueryException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public QueryException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public QueryException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/UnknownSessionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | /** 19 | * Indicates that an operation or other request from an unknown session was received. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class UnknownSessionException extends CopycatException { 24 | private static final CopycatError.Type TYPE = CopycatError.Type.UNKNOWN_SESSION_ERROR; 25 | 26 | public UnknownSessionException(String message, Object... args) { 27 | super(TYPE, message, args); 28 | } 29 | 30 | public UnknownSessionException(Throwable cause, String message, Object... args) { 31 | super(TYPE, cause, message, args); 32 | } 33 | 34 | public UnknownSessionException(Throwable cause) { 35 | super(TYPE, cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/error/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * Provides error constants and exceptions associated with the Raft consensus protocol. 19 | *

    20 | * Copycat protocol errors are designed to be transported across networks in the most efficient manner possible. Each protocol exception 21 | * is associated with a 1-byte identifier. Rather than serializing complete {@link java.lang.Exception} objects, the protocol exception is 22 | * sent using only its identifier and the exception is recreated by the receiving side of a connection. 23 | * 24 | * @author Jordan Halterman 25 | */ 26 | package io.atomix.copycat.error; 27 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * Core interfaces for operating on replicated state machines in the Copycat cluster. 19 | *

    20 | * The interfaces in this package are shared by both clients and servers. They are the interfaces through which clients and servers communicate 21 | * state change and query information with one another. 22 | *

    23 | * Clients operate on Copycat replicated state machines by submitting state change {@link io.atomix.copycat.Operation operations} to the cluster. 24 | * Copycat supports separate operations - {@link io.atomix.copycat.Command} and {@link io.atomix.copycat.Query} - for submitting state change 25 | * and read-only operations respectively. Each operation maps to a method of a replicated state machine. The handling of operations is dependent 26 | * on a variety of factors, including the operation type and the operation's {@link io.atomix.copycat.Query.ConsistencyLevel consistency level}. 27 | * When an operation is submitted to the cluster, the operation will eventually be translated into a method call on the replicated state machine 28 | * and a response will be sent back to the client. 29 | *

    Commands

    30 | * {@link io.atomix.copycat.Command Commands} are operations that modify the state of the replicated state machine. A command is a serializable 31 | * object that will be sent to the leader of the cluster and replicated to a persisted on a majority of the servers in the Copycat cluster before 32 | * being applied to the state machine. Once a command is committed (stored on a majority of servers), it's translated into a method call on the 33 | * state machine on each server. The return value of the state machine method on the leader is sent back to the client. 34 | *

    Queries

    35 | * {@link io.atomix.copycat.Query Queries} are operations that read but do not modify the state of the replicated state machine. Because queries 36 | * do not effect the state of the system, servers do not have to replicate them to a majority of the cluster, and no disk I/O is necessary to 37 | * complete a query of the state machine's state. Like commands, queries translate to a method call on the replicated state machine, but only 38 | * the server to which the query is submitted applies the query to its state machine. Once a query is completed, the return value of the state 39 | * machine method called is sent back to the client. 40 | * 41 | * @author Jordan Halterman 42 | */ 43 | package io.atomix.copycat; 44 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/AbstractRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | 22 | import java.util.Objects; 23 | 24 | /** 25 | * Base request for all client requests. 26 | * 27 | * @author Jordan Halterman 28 | */ 29 | public abstract class AbstractRequest implements Request { 30 | 31 | @Override 32 | public void writeObject(BufferOutput buffer, Serializer serializer) { 33 | } 34 | 35 | @Override 36 | public void readObject(BufferInput buffer, Serializer serializer) { 37 | } 38 | 39 | /** 40 | * Abstract request builder. 41 | * 42 | * @param The builder type. 43 | * @param The request type. 44 | */ 45 | protected static abstract class Builder, U extends AbstractRequest> implements Request.Builder { 46 | protected final U request; 47 | 48 | /** 49 | * @throws NullPointerException if {@code pool} or {@code factory} are null 50 | */ 51 | protected Builder(U request) { 52 | this.request = request; 53 | } 54 | 55 | @Override 56 | public U build() { 57 | return request; 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(request); 63 | } 64 | 65 | @Override 66 | public boolean equals(Object object) { 67 | return getClass().isAssignableFrom(object.getClass()) && ((Builder) object).request.equals(request); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return String.format("%s[request=%s]", getClass().getCanonicalName(), request); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/ClientRequestTypeResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import io.atomix.catalyst.serializer.SerializableTypeResolver; 19 | import io.atomix.catalyst.serializer.SerializerRegistry; 20 | 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * Request serializable type resolver. 26 | * 27 | * @author , Integer> TYPES = new HashMap() {{ 32 | put(CommandRequest.class, -3); 33 | put(ConnectRequest.class, -4); 34 | put(KeepAliveRequest.class, -5); 35 | put(PublishRequest.class, -6); 36 | put(QueryRequest.class, -7); 37 | put(RegisterRequest.class, -8); 38 | put(UnregisterRequest.class, -9); 39 | }}; 40 | 41 | @Override 42 | public void resolve(SerializerRegistry registry) { 43 | for (Map.Entry, Integer> entry : TYPES.entrySet()) { 44 | registry.register(entry.getKey(), entry.getValue()); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/ClientResponseTypeResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import io.atomix.catalyst.serializer.SerializableTypeResolver; 19 | import io.atomix.catalyst.serializer.SerializerRegistry; 20 | 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * Response serializable type resolver. 26 | * 27 | * @author , Integer> TYPES = new HashMap() {{ 32 | put(CommandResponse.class, -10); 33 | put(ConnectResponse.class, -11); 34 | put(KeepAliveResponse.class, -12); 35 | put(ResetRequest.class, -13); 36 | put(QueryResponse.class, -14); 37 | put(RegisterResponse.class, -15); 38 | put(UnregisterResponse.class, -16); 39 | }}; 40 | 41 | @Override 42 | public void resolve(SerializerRegistry registry) { 43 | for (Map.Entry, Integer> entry : TYPES.entrySet()) { 44 | registry.register(entry.getKey(), entry.getValue()); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/CommandResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | /** 19 | * Client command response. 20 | *

    21 | * Command responses are sent by servers to clients upon the completion of a 22 | * {@link CommandRequest}. Command responses are sent with the 23 | * {@link #index()} (or index) of the state machine at the point at which the command was evaluated. 24 | * This can be used by the client to ensure it sees state progress monotonically. Note, however, that 25 | * command responses may not be sent or received in sequential order. If a command response has to await 26 | * the completion of an event, or if the response is proxied through another server, responses may be 27 | * received out of order. Clients should resequence concurrent responses to ensure they're handled in FIFO order. 28 | * 29 | * @author Jordan Halterman 30 | */ 31 | public class CommandResponse extends OperationResponse { 32 | 33 | /** 34 | * Returns a new submit response builder. 35 | * 36 | * @return A new submit response builder. 37 | */ 38 | public static Builder builder() { 39 | return new Builder(new CommandResponse()); 40 | } 41 | 42 | /** 43 | * Returns a submit response builder for an existing request. 44 | * 45 | * @param response The response to build. 46 | * @return The submit response builder. 47 | * @throws NullPointerException if {@code request} is null 48 | */ 49 | public static Builder builder(CommandResponse response) { 50 | return new Builder(response); 51 | } 52 | 53 | /** 54 | * Command response builder. 55 | */ 56 | public static class Builder extends OperationResponse.Builder { 57 | protected Builder(CommandResponse response) { 58 | super(response); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/QueryResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | /** 19 | * Client query response. 20 | *

    21 | * Query responses are sent by servers to clients upon the completion of a 22 | * {@link QueryRequest}. Query responses are sent with the 23 | * {@link #index()} of the state machine at the point at which the query was evaluated. 24 | * This can be used by the client to ensure it sees state progress monotonically. Note, however, that 25 | * query responses may not be sent or received in sequential order. If a query response is proxied through 26 | * another server, responses may be received out of order. Clients should resequence concurrent responses 27 | * to ensure they're handled in FIFO order. 28 | * 29 | * @author Jordan Halterman 30 | */ 31 | public class QueryResponse extends OperationResponse { 32 | 33 | /** 34 | * Returns a new query response builder. 35 | * 36 | * @return A new query response builder. 37 | */ 38 | public static Builder builder() { 39 | return new Builder(new QueryResponse()); 40 | } 41 | 42 | /** 43 | * Returns a query response builder for an existing request. 44 | * 45 | * @param response The response to build. 46 | * @return The query response builder. 47 | * @throws NullPointerException if {@code request} is null 48 | */ 49 | public static Builder builder(QueryResponse response) { 50 | return new Builder(response); 51 | } 52 | 53 | /** 54 | * Query response builder. 55 | */ 56 | public static class Builder extends OperationResponse.Builder { 57 | protected Builder(QueryResponse response) { 58 | super(response); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import io.atomix.catalyst.serializer.CatalystSerializable; 19 | 20 | /** 21 | * Base interface for requests. 22 | * 23 | * @author Jordan Halterman 24 | */ 25 | public interface Request extends CatalystSerializable { 26 | 27 | /** 28 | * Request builder. 29 | * 30 | * @param The builder type. 31 | */ 32 | interface Builder, U extends Request> extends io.atomix.catalyst.util.Builder { 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/SessionRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.Assert; 22 | 23 | /** 24 | * Base session request. 25 | *

    26 | * This is the base request for session-related requests. Many client requests are handled within the 27 | * context of a {@link #session()} identifier. 28 | * 29 | * @author Jordan Halterman 30 | */ 31 | public abstract class SessionRequest extends AbstractRequest { 32 | protected long session; 33 | 34 | /** 35 | * Returns the session ID. 36 | * 37 | * @return The session ID. 38 | */ 39 | public long session() { 40 | return session; 41 | } 42 | 43 | @Override 44 | public void readObject(BufferInput buffer, Serializer serializer) { 45 | session = buffer.readLong(); 46 | } 47 | 48 | @Override 49 | public void writeObject(BufferOutput buffer, Serializer serializer) { 50 | buffer.writeLong(session); 51 | } 52 | 53 | /** 54 | * Session request builder. 55 | */ 56 | public static abstract class Builder, U extends SessionRequest> extends AbstractRequest.Builder { 57 | protected Builder(U request) { 58 | super(request); 59 | } 60 | 61 | /** 62 | * Sets the session ID. 63 | * 64 | * @param session The session ID. 65 | * @return The request builder. 66 | * @throws IllegalArgumentException if {@code session} is less than 0 67 | */ 68 | @SuppressWarnings("unchecked") 69 | public T withSession(long session) { 70 | request.session = Assert.argNot(session, session < 1, "session cannot be less than 1"); 71 | return (T) this; 72 | } 73 | 74 | @Override 75 | public U build() { 76 | super.build(); 77 | Assert.stateNot(request.session < 1, "session cannot be less than 1"); 78 | return request; 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/SessionResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | /** 19 | * Base session response. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public abstract class SessionResponse extends AbstractResponse { 24 | 25 | /** 26 | * Session response builder. 27 | */ 28 | public static abstract class Builder, U extends SessionResponse> extends AbstractResponse.Builder { 29 | protected Builder(U response) { 30 | super(response); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/UnregisterRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.protocol; 17 | 18 | import java.util.Objects; 19 | 20 | /** 21 | * Session unregister request. 22 | *

    23 | * The unregister request is sent by a client with an open session to the cluster to explicitly 24 | * unregister its session. Note that if a client does not send an unregister request, its session will 25 | * eventually expire. The unregister request simply provides a more orderly method for closing client sessions. 26 | * 27 | * @author Jordan Halterman 28 | */ 29 | public class UnregisterRequest extends SessionRequest { 30 | 31 | /** 32 | * Returns a new unregister request builder. 33 | * 34 | * @return A new unregister request builder. 35 | */ 36 | public static Builder builder() { 37 | return new Builder(new UnregisterRequest()); 38 | } 39 | 40 | /** 41 | * Returns a unregister request builder for an existing request. 42 | * 43 | * @param request The request to build. 44 | * @return The unregister request builder. 45 | * @throws NullPointerException if {@code request} is null 46 | */ 47 | public static Builder builder(UnregisterRequest request) { 48 | return new Builder(request); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(getClass(), session); 54 | } 55 | 56 | @Override 57 | public boolean equals(Object object) { 58 | if (object instanceof UnregisterRequest) { 59 | UnregisterRequest request = (UnregisterRequest) object; 60 | return request.session == session; 61 | } 62 | return false; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return String.format("%s[session=%d]", getClass().getSimpleName(), session); 68 | } 69 | 70 | /** 71 | * Unregister request builder. 72 | */ 73 | public static class Builder extends SessionRequest.Builder { 74 | protected Builder(UnregisterRequest request) { 75 | super(request); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/protocol/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * {@link io.atomix.copycat.protocol.Request} and {@link io.atomix.copycat.protocol.Response} implementations for all 19 | * client-server communication. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | package io.atomix.copycat.protocol; 24 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/session/ClosedSessionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.session; 17 | 18 | /** 19 | * Closed session exception. 20 | * 21 | * @author 26 | * Events are published by server state machines to client sessions as event objects. Each event sent to a session is 27 | * associated with a {@link String} event name and value. 28 | * 29 | * @see Session 30 | * 31 | * @author buffer, Serializer serializer) { 66 | buffer.writeUTF8(event); 67 | serializer.writeObject(message, buffer); 68 | } 69 | 70 | @Override 71 | public void readObject(BufferInput buffer, Serializer serializer) { 72 | event = buffer.readUTF8(); 73 | message = serializer.readObject(buffer); 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return String.format("%s[event=%s, message=%s]", getClass().getSimpleName(), event, message); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/session/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * Classes and interfaces for managing client sessions. 19 | *

    20 | * Sessions represent the context in which clients communicate with a Raft cluster. Sessions allow clusters 21 | * to process requests according to client state. For instance, sessions are responsible for ensuring operations 22 | * are executed in the order specified by the client (sequential consistency) and operations are only applied 23 | * to the replicated state machine once (linearizability). 24 | * 25 | * @author Jordan Halterman 26 | */ 27 | package io.atomix.copycat.session; 28 | -------------------------------------------------------------------------------- /protocol/src/main/java/io/atomix/copycat/util/ProtocolSerialization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.util; 17 | 18 | import io.atomix.catalyst.serializer.SerializableTypeResolver; 19 | import io.atomix.catalyst.serializer.SerializerRegistry; 20 | import io.atomix.catalyst.transport.Address; 21 | import io.atomix.copycat.NoOpCommand; 22 | import io.atomix.copycat.protocol.Request; 23 | import io.atomix.copycat.session.Event; 24 | 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * Session serializable type resolver. 30 | * 31 | * @author , Integer> TYPES = new HashMap() {{ 36 | put(Address.class, -1); 37 | put(Event.class, -2); 38 | put(NoOpCommand.class, -45); 39 | }}; 40 | 41 | @Override 42 | public void resolve(SerializerRegistry registry) { 43 | for (Map.Entry, Integer> entry : TYPES.entrySet()) { 44 | registry.register(entry.getKey(), entry.getValue()); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/services/io.atomix.catalyst.serializer.CatalystSerializable: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 the original author or authors. 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 | io.atomix.copycat.NoOpCommand 17 | io.atomix.copycat.session.Event 18 | io.atomix.copycat.protocol.CommandRequest 19 | io.atomix.copycat.protocol.CommandResponse 20 | io.atomix.copycat.protocol.KeepAliveRequest 21 | io.atomix.copycat.protocol.KeepAliveResponse 22 | io.atomix.copycat.protocol.PublishRequest 23 | io.atomix.copycat.protocol.ResetRequest 24 | io.atomix.copycat.protocol.QueryRequest 25 | io.atomix.copycat.protocol.QueryResponse 26 | io.atomix.copycat.protocol.RegisterRequest 27 | io.atomix.copycat.protocol.RegisterResponse 28 | io.atomix.copycat.protocol.ConnectRequest 29 | io.atomix.copycat.protocol.ConnectResponse 30 | io.atomix.copycat.protocol.UnregisterRequest 31 | io.atomix.copycat.protocol.UnregisterResponse 32 | -------------------------------------------------------------------------------- /protocol/src/test/java/io/atomix/copycat/error/CopycatErrorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.error; 17 | 18 | import org.testng.annotations.Test; 19 | 20 | import static org.testng.Assert.assertEquals; 21 | 22 | /** 23 | * Copycat error serialization test. 24 | * 25 | * @author 16 | 17 | 4.0.0 18 | 19 | 20 | io.atomix.copycat 21 | copycat-parent 22 | 1.2.9-SNAPSHOT 23 | 24 | 25 | bundle 26 | copycat-server 27 | Copycat Server 28 | 29 | 30 | 31 | io.atomix.copycat 32 | copycat-protocol 33 | ${project.version} 34 | 35 | 36 | io.atomix.catalyst 37 | catalyst-local 38 | ${catalyst.version} 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | org.apache.maven.plugins 53 | maven-jar-plugin 54 | 55 | 56 | 57 | test-jar 58 | 59 | 60 | 61 | test-jar-on-test-compile 62 | test-compile 63 | 64 | test-jar 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/StateMachineContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server; 18 | 19 | import io.atomix.copycat.Command; 20 | import io.atomix.copycat.Query; 21 | import io.atomix.copycat.server.session.Sessions; 22 | 23 | import java.time.Clock; 24 | 25 | /** 26 | * State machine context. 27 | *

    28 | * The context is reflective of the current position and state of the Raft state machine. In particular, 29 | * it exposes the current approximate {@link StateMachineContext#clock() time} and all open 30 | * {@link Sessions}. 31 | * 32 | * @author Jordan Halterman 21 | */ 22 | package io.atomix.copycat.server.cluster; 23 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * Standalone, feature-complete implementation of the Raft consensus algorithm. 19 | *

    20 | * For information on the implementation of the Raft consensus algorithm, see the documentation on 21 | * Copycat internals. 22 | * 23 | * @author Jordan Halterman 24 | */ 25 | package io.atomix.copycat.server; 26 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/JoinRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.protocol; 17 | 18 | /** 19 | * Server join configuration change request. 20 | *

    21 | * The join request is the mechanism by which new servers join a cluster. When a server wants to 22 | * join a cluster, it must submit a join request to the leader. The leader will attempt to commit 23 | * the configuration change and, if successful, respond to the join request with the updated configuration. 24 | * 25 | * @author Jordan Halterman 26 | */ 27 | public class JoinRequest extends ConfigurationRequest { 28 | 29 | /** 30 | * Returns a new join request builder. 31 | * 32 | * @return A new join request builder. 33 | */ 34 | public static Builder builder() { 35 | return new Builder(new JoinRequest()); 36 | } 37 | 38 | /** 39 | * Returns an join request builder for an existing request. 40 | * 41 | * @param request The request to build. 42 | * @return The join request builder. 43 | */ 44 | public static Builder builder(JoinRequest request) { 45 | return new Builder(request); 46 | } 47 | 48 | /** 49 | * Join request builder. 50 | */ 51 | public static class Builder extends ConfigurationRequest.Builder { 52 | protected Builder(JoinRequest request) { 53 | super(request); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/JoinResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.protocol; 17 | 18 | import io.atomix.copycat.protocol.Response; 19 | 20 | /** 21 | * Server join configuration change response. 22 | *

    23 | * Join responses are sent in response to a request to add a server to the cluster configuration. If a 24 | * configuration change is failed due to a conflict, the response status will be 25 | * {@link Response.Status#ERROR} but the response {@link #error()} will 26 | * be {@code null}. 27 | * 28 | * @author Jordan Halterman 29 | */ 30 | public class JoinResponse extends ConfigurationResponse { 31 | 32 | /** 33 | * Returns a new join response builder. 34 | * 35 | * @return A new join response builder. 36 | */ 37 | public static Builder builder() { 38 | return new Builder(new JoinResponse()); 39 | } 40 | 41 | /** 42 | * Returns an join response builder for an existing response. 43 | * 44 | * @param response The response to build. 45 | * @return The join response builder. 46 | */ 47 | public static Builder builder(JoinResponse response) { 48 | return new Builder(response); 49 | } 50 | 51 | /** 52 | * Join response builder. 53 | */ 54 | public static class Builder extends ConfigurationResponse.Builder { 55 | protected Builder(JoinResponse response) { 56 | super(response); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/LeaveRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.protocol; 17 | 18 | /** 19 | * Server leave configuration request. 20 | *

    21 | * The leave request is the mechanism by which servers remove themselves from a cluster. When a server 22 | * wants to leave a cluster, it must submit a leave request to the leader. The leader will attempt to commit 23 | * the configuration change and, if successful, respond to the join request with the updated configuration. 24 | * 25 | * @author Jordan Halterman 26 | */ 27 | public class LeaveRequest extends ConfigurationRequest { 28 | 29 | /** 30 | * Returns a new leave request builder. 31 | * 32 | * @return A new leave request builder. 33 | */ 34 | public static Builder builder() { 35 | return new Builder(new LeaveRequest()); 36 | } 37 | 38 | /** 39 | * Returns an leave request builder for an existing request. 40 | * 41 | * @param request The request to build. 42 | * @return The leave request builder. 43 | */ 44 | public static Builder builder(LeaveRequest request) { 45 | return new Builder(request); 46 | } 47 | 48 | /** 49 | * Leave request builder. 50 | */ 51 | public static class Builder extends ConfigurationRequest.Builder { 52 | protected Builder(LeaveRequest request) { 53 | super(request); 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/LeaveResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.protocol; 17 | 18 | import io.atomix.copycat.protocol.Response; 19 | 20 | /** 21 | * Server leave configuration change response. 22 | *

    23 | * Leave responses are sent in response to a request to add a server to the cluster configuration. If a 24 | * configuration change is failed due to a conflict, the response status will be 25 | * {@link Response.Status#ERROR} but the response {@link #error()} will 26 | * be {@code null}. 27 | * 28 | * @author Jordan Halterman 29 | */ 30 | public class LeaveResponse extends ConfigurationResponse { 31 | 32 | /** 33 | * Returns a new leave response builder. 34 | * 35 | * @return A new leave response builder. 36 | */ 37 | public static Builder builder() { 38 | return new Builder(new LeaveResponse()); 39 | } 40 | 41 | /** 42 | * Returns an leave response builder for an existing response. 43 | * 44 | * @param response The response to build. 45 | * @return The leave response builder. 46 | */ 47 | public static Builder builder(LeaveResponse response) { 48 | return new Builder(response); 49 | } 50 | 51 | /** 52 | * Leave response builder. 53 | */ 54 | public static class Builder extends ConfigurationResponse.Builder { 55 | protected Builder(LeaveResponse response) { 56 | super(response); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/ReconfigureResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.protocol; 17 | 18 | /** 19 | * Server configuration change response. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class ReconfigureResponse extends ConfigurationResponse { 24 | 25 | /** 26 | * Returns a new reconfigure response builder. 27 | * 28 | * @return A new reconfigure response builder. 29 | */ 30 | public static Builder builder() { 31 | return new Builder(new ReconfigureResponse()); 32 | } 33 | 34 | /** 35 | * Returns a reconfigure response builder for an existing response. 36 | * 37 | * @param response The response to build. 38 | * @return The reconfigure response builder. 39 | */ 40 | public static Builder builder(ReconfigureResponse response) { 41 | return new Builder(response); 42 | } 43 | 44 | /** 45 | * Reconfigure response builder. 46 | */ 47 | public static class Builder extends ConfigurationResponse.Builder { 48 | protected Builder(ReconfigureResponse response) { 49 | super(response); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/protocol/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * {@link io.atomix.copycat.protocol.Request} and {@link io.atomix.copycat.protocol.Response} implementations for 19 | * server-to-server communication. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | package io.atomix.copycat.server.protocol; 24 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/session/Sessions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.session; 18 | 19 | /** 20 | * Provides a set of active server sessions. 21 | *

    22 | * Server state machines can use the {@code Sessions} object to access the list of sessions currently open to the 23 | * state machine. Session sets are guaranteed to be deterministic. All state machines will see the same set of 24 | * open sessions at the same point in the log except in cases where a session has already been closed and removed. 25 | * If a session has already been closed on another server, the session is guaranteed to have been expired on all 26 | * servers and thus operations like {@link ServerSession#publish(String, Object)} are effectively no-ops. 27 | * 28 | * @author 20 | * Client session information is exposed to server {@link io.atomix.copycat.server.StateMachine state machines} in the form of a 21 | * {@link io.atomix.copycat.server.session.ServerSession} object. Server sessions provide functionality in addition to normal session state 22 | * information to allow replicated state machines to {@link io.atomix.copycat.server.session.ServerSession#publish(java.lang.String, java.lang.Object) publish} 23 | * event notifications to clients through their session. 24 | * 25 | * @author Jordan Halterman 26 | */ 27 | package io.atomix.copycat.server.session; 28 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/state/FollowerAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.state; 17 | 18 | import io.atomix.copycat.server.cluster.Member; 19 | 20 | /** 21 | * Follower appender. 22 | * 23 | * @author Jordan Halterman 31 | */ 32 | final class ServerCommitPool implements AutoCloseable { 33 | private static final Logger LOGGER = LoggerFactory.getLogger(ServerCommitPool.class); 34 | private final Log log; 35 | private final ServerSessionManager sessions; 36 | private final Queue pool = new ConcurrentLinkedQueue<>(); 37 | 38 | public ServerCommitPool(Log log, ServerSessionManager sessions) { 39 | this.log = Assert.notNull(log, "log"); 40 | this.sessions = Assert.notNull(sessions, "sessions"); 41 | } 42 | 43 | /** 44 | * Acquires a commit from the pool. 45 | * 46 | * @param entry The entry for which to acquire the commit. 47 | * @return The commit to acquire. 48 | */ 49 | public ServerCommit acquire(OperationEntry entry, ServerSessionContext session, long timestamp) { 50 | ServerCommit commit = pool.poll(); 51 | if (commit == null) { 52 | commit = new ServerCommit(this, log); 53 | } 54 | commit.reset(entry, session, timestamp); 55 | return commit; 56 | } 57 | 58 | /** 59 | * Releases a commit back to the pool. 60 | * 61 | * @param commit The commit to release. 62 | */ 63 | public void release(ServerCommit commit) { 64 | pool.add(commit); 65 | } 66 | 67 | /** 68 | * Issues a warning that the given commit was garbage collected. 69 | * 70 | * @param commit The commit that was garbage collected. 71 | */ 72 | public void warn(ServerCommit commit) { 73 | LOGGER.trace("Server commit " + commit + " was garbage collected!\nCommit log is dirty!"); 74 | } 75 | 76 | @Override 77 | public void close() { 78 | pool.clear(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/state/ServerStateMachineContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.state; 18 | 19 | import io.atomix.copycat.server.StateMachineContext; 20 | 21 | import java.time.Clock; 22 | import java.time.Instant; 23 | 24 | /** 25 | * Server state machine context. 26 | * 27 | * @author Jordan Halterman 21 | */ 22 | package io.atomix.copycat.server.state; 23 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/DescriptorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | /** 19 | * Segment descriptor exception. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class DescriptorException extends StorageException { 24 | 25 | public DescriptorException(String message, Object... args) { 26 | super(String.format(message, args)); 27 | } 28 | 29 | public DescriptorException(Throwable cause, String message, Object... args) { 30 | super(String.format(message, args), cause); 31 | } 32 | 33 | public DescriptorException(Throwable cause) { 34 | super(cause); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/StorageCleaner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | import io.atomix.catalyst.util.Assert; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.file.Files; 23 | import java.util.function.Predicate; 24 | 25 | /** 26 | * Handles deletion of files. 27 | * 28 | * @author predicate) { 41 | // Ensure storage directories are created. 42 | storage.directory().mkdirs(); 43 | 44 | // Iterate through all files in the storage directory. 45 | for (File file : storage.directory().listFiles(f -> f.isFile() && predicate.test(f))) { 46 | try { 47 | Files.delete(file.toPath()); 48 | } catch (IOException e) { 49 | // Ignore the exception. 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/StorageException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | /** 19 | * Log exception. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | public class StorageException extends RuntimeException { 24 | 25 | public StorageException() { 26 | } 27 | 28 | public StorageException(String message) { 29 | super(message); 30 | } 31 | 32 | public StorageException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | public StorageException(Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/StorageLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | /** 19 | * {@link Log} storage level configuration values which control how logs are stored on disk or in memory. 20 | *

    21 | * Storage levels represent the method used to store the individual {@link Segment segments} that make up a 22 | * {@link Log}. When configuring a {@link Storage} module, the storage can be configured to write 23 | * {@link io.atomix.copycat.server.storage.entry.Entry entries} to disk, memory, or memory-mapped files using the 24 | * values provided by this enum. The {@code StorageLevel} configuration dictates the type of 25 | * {@link io.atomix.catalyst.buffer.Buffer} to which to write entries. See the specific storage levels for more 26 | * information. 27 | * 28 | * @see Storage 29 | * 30 | * @author 26 | * Compaction managers are responsible for providing a set of {@link CompactionTask}s to be executed 27 | * during log compaction. Each {@link Compaction} type is associated with a compaction manager. 28 | * 29 | * @author 23 | * Compaction tasks are responsible for compacting one or more segments in a 24 | * {@link io.atomix.copycat.server.storage.Log}. Tasks are provided by a related {@link CompactionManager} 25 | * and are run in parallel in a pool of {@link Storage#compactionThreads()} background threads. 26 | * 27 | * @author 20 | * The log compaction package implements compaction for Copycat {@link io.atomix.copycat.server.storage.Log logs} using 21 | * a custom log cleaning algorithm. As entries are written to the log and applied to the server's state machine, the 22 | * state machine can arbitrarily mark entries for removal from the log. Periodically, a set of log compaction threads 23 | * will compact {@link io.atomix.copycat.server.storage.Segment segment}s of the log in the background. Log compaction 24 | * is performed in two phases: {@link io.atomix.copycat.server.storage.compaction.MinorCompactionTask minor} and 25 | * {@link io.atomix.copycat.server.storage.compaction.MajorCompactionTask major}. The minor compaction process efficiently 26 | * rewrites individual segments to remove standard entries that have been marked for cleaning. The major compaction process 27 | * periodically rewrites the entire log to combine segments that have previously been compacted. 28 | * 29 | * @author Jordan Halterman 30 | */ 31 | package io.atomix.copycat.server.storage.compaction; 32 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/CommandEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.Assert; 22 | import io.atomix.catalyst.util.reference.ReferenceManager; 23 | import io.atomix.copycat.Command; 24 | import io.atomix.copycat.Operation; 25 | import io.atomix.copycat.server.storage.compaction.Compaction; 26 | 27 | /** 28 | * Stores a state machine {@link Command}. 29 | *

    30 | * The {@code CommandEntry} is used to store an individual state machine command from an individual 31 | * client along with information relevant to sequencing the command in the server state machine. 32 | * 33 | * @author Jordan Halterman 34 | */ 35 | public class CommandEntry extends OperationEntry { 36 | private Command command; 37 | 38 | public CommandEntry() { 39 | } 40 | 41 | public CommandEntry(ReferenceManager> referenceManager) { 42 | super(referenceManager); 43 | } 44 | 45 | @Override 46 | public Compaction.Mode getCompactionMode() { 47 | return Compaction.Mode.valueOf(command.compaction().name()); 48 | } 49 | 50 | @Override 51 | public Operation getOperation() { 52 | return command; 53 | } 54 | 55 | /** 56 | * Returns the command. 57 | * 58 | * @return The command. 59 | */ 60 | public Command getCommand() { 61 | return command; 62 | } 63 | 64 | /** 65 | * Sets the command. 66 | * 67 | * @param command The command. 68 | * @return The command entry. 69 | * @throws NullPointerException if {@code command} is null 70 | */ 71 | public CommandEntry setCommand(Command command) { 72 | this.command = Assert.notNull(command, "command"); 73 | return this; 74 | } 75 | 76 | @Override 77 | public void writeObject(BufferOutput buffer, Serializer serializer) { 78 | super.writeObject(buffer, serializer); 79 | serializer.writeObject(command, buffer); 80 | } 81 | 82 | @Override 83 | public void readObject(BufferInput buffer, Serializer serializer) { 84 | super.readObject(buffer, serializer); 85 | command = serializer.readObject(buffer); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return String.format("%s[index=%d, term=%d, session=%d, sequence=%d, timestamp=%d, command=%s]", getClass().getSimpleName(), getIndex(), getTerm(), getSession(), getSequence(), getTimestamp(), command); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/ConfigurationEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.Assert; 22 | import io.atomix.catalyst.util.reference.ReferenceManager; 23 | import io.atomix.copycat.server.cluster.Member; 24 | import io.atomix.copycat.server.storage.compaction.Compaction; 25 | 26 | import java.util.Collection; 27 | 28 | /** 29 | * Stores a cluster configuration. 30 | *

    31 | * The {@code ConfigurationEntry} stores information relevant to a single cluster configuration change. 32 | * Configuration change entries store a collection of {@link Member members} which each represent a 33 | * server in the cluster. Each time the set of members changes or a property of a single member changes, 34 | * a new {@code ConfigurationEntry} must be logged for the configuration change. 35 | * 36 | * @author Jordan Halterman 37 | */ 38 | public class ConfigurationEntry extends TimestampedEntry { 39 | private Collection members; 40 | 41 | public ConfigurationEntry() { 42 | } 43 | 44 | public ConfigurationEntry(ReferenceManager> referenceManager) { 45 | super(referenceManager); 46 | } 47 | 48 | @Override 49 | public Compaction.Mode getCompactionMode() { 50 | return Compaction.Mode.FULL; 51 | } 52 | 53 | /** 54 | * Returns the members. 55 | * 56 | * @return The members. 57 | */ 58 | public Collection getMembers() { 59 | return members; 60 | } 61 | 62 | /** 63 | * Sets the members. 64 | * 65 | * @param members The members. 66 | * @return The configuration entry. 67 | * @throws NullPointerException if {@code members} is null 68 | */ 69 | public ConfigurationEntry setMembers(Collection members) { 70 | this.members = Assert.notNull(members, "members"); 71 | return this; 72 | } 73 | 74 | @Override 75 | public void writeObject(BufferOutput buffer, Serializer serializer) { 76 | super.writeObject(buffer, serializer); 77 | serializer.writeObject(members, buffer); 78 | } 79 | 80 | @Override 81 | public void readObject(BufferInput buffer, Serializer serializer) { 82 | super.readObject(buffer, serializer); 83 | members = serializer.readObject(buffer); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return String.format("%s[index=%d, term=%d, timestamp=%d, members=%s]", getClass().getSimpleName(), getIndex(), getTerm(), getTimestamp(), members); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/InitializeEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.util.reference.ReferenceManager; 19 | import io.atomix.copycat.server.storage.compaction.Compaction; 20 | 21 | /** 22 | * Indicates a leader change has occurred. 23 | *

    24 | * The {@code InitializeEntry} is logged by a leader at the beginning of its term to indicate that 25 | * a leadership change has occurred. Importantly, initialize entries are logged with a {@link #getTimestamp() timestamp} 26 | * which can be used by server state machines to reset session timeouts following leader changes. Initialize entries 27 | * are always the first entry to be committed at the start of a leader's term. 28 | * 29 | * @author Jordan Halterman 30 | */ 31 | public class InitializeEntry extends TimestampedEntry { 32 | 33 | public InitializeEntry() { 34 | } 35 | 36 | public InitializeEntry(ReferenceManager> referenceManager) { 37 | super(referenceManager); 38 | } 39 | 40 | @Override 41 | public Compaction.Mode getCompactionMode() { 42 | return Compaction.Mode.FULL; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return String.format("%s[index=%d, term=%d, timestamp=%s]", getClass().getSimpleName(), getIndex(), getTerm(), getTimestamp()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/OperationEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.copycat.Operation; 19 | import io.atomix.catalyst.buffer.BufferInput; 20 | import io.atomix.catalyst.buffer.BufferOutput; 21 | import io.atomix.catalyst.serializer.Serializer; 22 | import io.atomix.catalyst.util.reference.ReferenceManager; 23 | 24 | /** 25 | * Stores a state machine operation. 26 | *

    27 | * Each state machine operation is stored with a client-provided {@link #getSequence() sequence number}. 28 | * The sequence number is used by state machines to apply client operations in the order in which they 29 | * were submitted by the client (FIFO order). Additionally, each operation is written with the leader's 30 | * {@link #getTimestamp() timestamp} at the time the entry was logged. This gives state machines an 31 | * approximation of time with which to react to the application of operations to the state machine. 32 | * 33 | * @author Jordan Halterman 34 | */ 35 | public abstract class OperationEntry> extends SessionEntry { 36 | private long sequence; 37 | 38 | protected OperationEntry() { 39 | } 40 | 41 | protected OperationEntry(ReferenceManager> referenceManager) { 42 | super(referenceManager); 43 | } 44 | 45 | /** 46 | * Returns the entry operation. 47 | * 48 | * @return The entry operation. 49 | */ 50 | public abstract Operation getOperation(); 51 | 52 | /** 53 | * Returns the operation sequence number. 54 | * 55 | * @return The operation sequence number. 56 | */ 57 | public long getSequence() { 58 | return sequence; 59 | } 60 | 61 | /** 62 | * Sets the operation sequence number. 63 | * 64 | * @param sequence The operation sequence number. 65 | * @return The operation entry. 66 | */ 67 | @SuppressWarnings("unchecked") 68 | public T setSequence(long sequence) { 69 | this.sequence = sequence; 70 | return (T) this; 71 | } 72 | 73 | @Override 74 | public void writeObject(BufferOutput buffer, Serializer serializer) { 75 | super.writeObject(buffer, serializer); 76 | buffer.writeLong(sequence); 77 | } 78 | 79 | @Override 80 | public void readObject(BufferInput buffer, Serializer serializer) { 81 | super.readObject(buffer, serializer); 82 | sequence = buffer.readLong(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/QueryEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.Assert; 22 | import io.atomix.catalyst.util.reference.ReferenceManager; 23 | import io.atomix.copycat.Operation; 24 | import io.atomix.copycat.Query; 25 | 26 | /** 27 | * Represents a state machine {@link Query}. 28 | *

    29 | * The {@code QueryEntry} is a special entry that is typically not ever written to the Raft log. 30 | * Query entries are simply used to represent the context within which a query is applied to the 31 | * state machine. Query entry {@link #getSequence() sequence} numbers and {@link #getIndex() indexes} 32 | * are used to sequence queries as they're applied to the user state machine. 33 | * 34 | * @author Jordan Halterman 35 | */ 36 | public class QueryEntry extends OperationEntry { 37 | private Query query; 38 | 39 | public QueryEntry() { 40 | } 41 | 42 | public QueryEntry(ReferenceManager> referenceManager) { 43 | super(referenceManager); 44 | } 45 | 46 | @Override 47 | public Operation getOperation() { 48 | return query; 49 | } 50 | 51 | /** 52 | * Returns the query. 53 | * 54 | * @return The query. 55 | */ 56 | public Query getQuery() { 57 | return query; 58 | } 59 | 60 | /** 61 | * Sets the query. 62 | * 63 | * @param query The query. 64 | * @return The query entry. 65 | * @throws NullPointerException if {@code query} is null 66 | */ 67 | public QueryEntry setQuery(Query query) { 68 | this.query = Assert.notNull(query, "query"); 69 | return this; 70 | } 71 | 72 | @Override 73 | public void writeObject(BufferOutput buffer, Serializer serializer) { 74 | super.writeObject(buffer, serializer); 75 | serializer.writeObject(query, buffer); 76 | } 77 | 78 | @Override 79 | public void readObject(BufferInput buffer, Serializer serializer) { 80 | super.readObject(buffer, serializer); 81 | query = serializer.readObject(buffer); 82 | } 83 | 84 | @Override 85 | public String toString() { 86 | return String.format("%s[index=%d, term=%d, session=%d, sequence=%d, timestamp=%d, query=%s]", getClass().getSimpleName(), getIndex(), getTerm(), getSession(), getSequence(), getTimestamp(), query); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/SessionEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.reference.ReferenceManager; 22 | 23 | /** 24 | * Base class for session-related entries. 25 | * 26 | * @author Jordan Halterman 27 | */ 28 | public abstract class SessionEntry> extends TimestampedEntry { 29 | private long session; 30 | 31 | protected SessionEntry() { 32 | } 33 | 34 | protected SessionEntry(ReferenceManager> referenceManager) { 35 | super(referenceManager); 36 | } 37 | 38 | /** 39 | * Sets the session ID. 40 | * 41 | * @param session The session ID. 42 | * @return The session entry. 43 | */ 44 | @SuppressWarnings("unchecked") 45 | public T setSession(long session) { 46 | this.session = session; 47 | return (T) this; 48 | } 49 | 50 | /** 51 | * Returns the session ID. 52 | * 53 | * @return The session ID. 54 | */ 55 | public long getSession() { 56 | return session; 57 | } 58 | 59 | @Override 60 | public void writeObject(BufferOutput buffer, Serializer serializer) { 61 | super.writeObject(buffer, serializer); 62 | buffer.writeLong(session); 63 | } 64 | 65 | @Override 66 | public void readObject(BufferInput buffer, Serializer serializer) { 67 | super.readObject(buffer, serializer); 68 | session = buffer.readLong(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/TimestampedEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.buffer.BufferInput; 19 | import io.atomix.catalyst.buffer.BufferOutput; 20 | import io.atomix.catalyst.serializer.Serializer; 21 | import io.atomix.catalyst.util.reference.ReferenceManager; 22 | 23 | /** 24 | * Base class for timestamped entries. 25 | * 26 | * @author Jordan Halterman 27 | */ 28 | public abstract class TimestampedEntry> extends Entry { 29 | private long timestamp; 30 | 31 | protected TimestampedEntry() { 32 | } 33 | 34 | protected TimestampedEntry(ReferenceManager> referenceManager) { 35 | super(referenceManager); 36 | } 37 | 38 | /** 39 | * Returns the entry timestamp. 40 | * 41 | * @return The entry timestamp. 42 | */ 43 | public long getTimestamp() { 44 | return timestamp; 45 | } 46 | 47 | /** 48 | * Sets the entry timestamp. 49 | * 50 | * @param timestamp The entry timestamp. 51 | * @return The entry. 52 | */ 53 | @SuppressWarnings("unchecked") 54 | public T setTimestamp(long timestamp) { 55 | this.timestamp = timestamp; 56 | return (T) this; 57 | } 58 | 59 | @Override 60 | public void writeObject(BufferOutput buffer, Serializer serializer) { 61 | super.writeObject(buffer, serializer); 62 | buffer.writeLong(timestamp); 63 | } 64 | 65 | @Override 66 | public void readObject(BufferInput buffer, Serializer serializer) { 67 | super.readObject(buffer, serializer); 68 | timestamp = buffer.readLong(); 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return String.format("%s[index=%d, term=%d, timestamp=%d]", getClass().getSimpleName(), getIndex(), getTerm(), timestamp); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/TypedEntryPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.entry; 17 | 18 | import io.atomix.catalyst.serializer.SerializationException; 19 | import io.atomix.catalyst.util.reference.ReferenceManager; 20 | import io.atomix.catalyst.util.reference.ReferencePool; 21 | import io.atomix.copycat.server.storage.StorageException; 22 | 23 | import java.lang.reflect.Constructor; 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * Type specific entry pool. 30 | * 31 | * @author Jordan Halterman 32 | */ 33 | public class TypedEntryPool { 34 | private final Map>> pools = new HashMap<>(); 35 | 36 | /** 37 | * Acquires a specific entry type. 38 | */ 39 | @SuppressWarnings("unchecked") 40 | public > T acquire(Class type, long index) { 41 | ReferencePool pool = (ReferencePool) pools.get(type); 42 | if (pool == null) { 43 | try { 44 | Constructor c = type.getConstructor(ReferenceManager.class); 45 | c.setAccessible(true); 46 | pool = new ReferencePool<>((r) -> { 47 | try { 48 | return c.newInstance(r); 49 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 50 | throw new StorageException(e); 51 | } 52 | }); 53 | } catch (NoSuchMethodException e) { 54 | throw new SerializationException("failed to instantiate reference: must provide a single argument constructor", e); 55 | } 56 | pools.put(type, pool); 57 | } 58 | 59 | T entry = pool.acquire(); 60 | entry.reset().setIndex(index); 61 | return entry; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/entry/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 | /** 18 | * {@link io.atomix.copycat.server.storage.entry.Entry} implementations used internally to replicate 19 | * state changes, sessions, and configuration changes in the cluster. 20 | * 21 | * @author Jordan Halterman 22 | */ 23 | package io.atomix.copycat.server.storage.entry; 24 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/index/DelegatingOffsetIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.storage.index; 17 | 18 | import io.atomix.catalyst.buffer.Buffer; 19 | 20 | /** 21 | * Offset index that delegates to other indexes. 22 | * 23 | * @author Raft implementation. 19 | *

    20 | * Logs are the vehicle through which Copycat servers persist and replicate state changes. The Copycat log is designed 21 | * specifically for use with the Raft consensus algorithm. The log is partitioned into multiple files called 22 | * segments. Each segment represents a sequence of indexes in the log. As entries are written to the log and 23 | * segments fill up, the log rolls over to new segments. Once a completed segment has been written and the entries 24 | * within it have been committed, the segment is compacted. 25 | *

    26 | * Log compaction is a two-stage process. The {@link io.atomix.copycat.server.storage.compaction.MinorCompactionTask minor compaction} 27 | * process periodically rewrites segments to remove non-tombstone and snapshot-related entries that have been released by the 28 | * state machine. The {@link io.atomix.copycat.server.storage.compaction.MajorCompactionTask major compaction} process periodically 29 | * rewrites segments to remove tombstones and combines multiple segments together to reduce the number of open file descriptors. 30 | *

    31 | * Copycat logs also support {@link io.atomix.copycat.server.storage.snapshot.SnapshotStore snapshotting}. Each snapshot 32 | * taken of the state machine's state is associated with a number of snapshotted entries. When segments are compacted, 33 | * entries compacted by the last snapshot are removed from segment files on disk. 34 | *

    35 | * For more information on Copycat's log and log compaction algorithms, see the 36 | * log documentation on the Atomix website. 37 | * 38 | * @author Jordan Halterman 39 | */ 40 | package io.atomix.copycat.server.storage; 41 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/snapshot/MemorySnapshot.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.snapshot; 17 | 18 | import io.atomix.catalyst.buffer.HeapBuffer; 19 | import io.atomix.catalyst.util.Assert; 20 | 21 | /** 22 | * In-memory snapshot backed by a {@link HeapBuffer}. 23 | * 24 | * @author 20 | * Snapshots are byte-level representations of the state machine's state taken periodically to allow entries 21 | * to be removed from Copycat's {@link io.atomix.copycat.server.storage.Log} during log compaction. 22 | * Copycat interacts with snapshots primarily through the {@link io.atomix.copycat.server.storage.snapshot.SnapshotStore} 23 | * which is responsible for storing and loading snapshots. Each snapshot is stored as a separate file 24 | * on disk, and old snapshots may or may not be retained depending on the {@link io.atomix.copycat.server.storage.Storage} 25 | * configuration. 26 | * 27 | * @author Jordan Halterman 28 | */ 29 | package io.atomix.copycat.server.storage.snapshot; 30 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/system/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.system; 17 | 18 | import io.atomix.catalyst.util.Assert; 19 | import io.atomix.copycat.server.cluster.Member; 20 | 21 | import java.util.Collection; 22 | 23 | /** 24 | * Represents a persisted server configuration. 25 | *

    26 | * This class represents a cluster configuration stored on disk. Configurations are managed by 27 | * a {@link MetaStore}, stored on disk when {@link io.atomix.copycat.server.cluster.Cluster Cluster} 28 | * configurations change, and loaded from disk on server startup. 29 | * 30 | * @author 48 | * The index is the index of the {@link io.atomix.copycat.server.storage.entry.ConfigurationEntry ConfigurationEntry} 49 | * which resulted in this configuration. 50 | * 51 | * @return The configuration index. 52 | */ 53 | public long index() { 54 | return index; 55 | } 56 | 57 | /** 58 | * Returns the configuration term. 59 | *

    60 | * The term is the term of the leader at the time the configuration change was committed. 61 | * 62 | * @return The configuration term. 63 | */ 64 | public long term() { 65 | return term; 66 | } 67 | 68 | /** 69 | * Returns the configuration time. 70 | * 71 | * @return The time at which the configuration was committed. 72 | */ 73 | public long time() { 74 | return time; 75 | } 76 | 77 | /** 78 | * Returns the cluster membership for this configuration. 79 | * 80 | * @return The cluster membership. 81 | */ 82 | public Collection members() { 83 | return members; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return String.format("%s[index=%d, time=%d, members=%s]", getClass().getSimpleName(), index, time, members); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/system/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 | /** 18 | * Classes and interfaces that aid in storing and loading persistent cluster and server configurations. 19 | *

    20 | * The {@link io.atomix.copycat.server.storage.system.MetaStore} is the primary interface through which servers 21 | * store and load server {@link io.atomix.copycat.server.storage.system.Configuration configurations} on disk. 22 | * Configurations include the current cluster configuration as well as the server's last 23 | * {@link io.atomix.copycat.server.storage.system.MetaStore#loadTerm() term} and 24 | * {@link io.atomix.copycat.server.storage.system.MetaStore#loadVote() vote} which must be persisted according 25 | * to the Raft consensus algorithm. 26 | * 27 | * @author Jordan Halterman 28 | */ 29 | package io.atomix.copycat.server.storage.system; 30 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/util/EntryBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.storage.util; 17 | 18 | import io.atomix.copycat.server.storage.entry.Entry; 19 | 20 | /** 21 | * Log entry buffer. 22 | * 23 | * @author T get(long index) { 57 | Entry entry = buffer[offset(index)]; 58 | return entry != null && entry.getIndex() == index ? (T) entry.acquire() : null; 59 | } 60 | 61 | /** 62 | * Clears the buffer and resets the index to the given index. 63 | * 64 | * @return The entry buffer. 65 | */ 66 | public EntryBuffer clear() { 67 | for (int i = 0; i < buffer.length; i++) { 68 | buffer[i] = null; 69 | } 70 | return this; 71 | } 72 | 73 | /** 74 | * Returns the buffer index for the given offset. 75 | */ 76 | private int offset(long index) { 77 | int offset = (int) (index % buffer.length); 78 | if (offset < 0) { 79 | offset += buffer.length; 80 | } 81 | return offset; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/util/OffsetPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage.util; 17 | 18 | import io.atomix.catalyst.buffer.util.BitArray; 19 | import io.atomix.catalyst.util.Assert; 20 | 21 | import java.util.function.Predicate; 22 | 23 | /** 24 | * Segment offset liveness predicate. 25 | *

    26 | * The offset predicate tracks the liveness of relative offsets within a segment. Liveness is tracked 27 | * in a {@link BitArray} that is resized to accommodate offsets as necessary. Each bit in the array 28 | * represents the liveness of a relative offset in the segment. When an offset is 29 | * {@link #release(long) released} from a segment, the bit at that offset is flipped in the bit array. 30 | * {@link #test(Long) Testing} the predicate indicates whether an offset is still live in the segment. 31 | * 32 | * @author , Integer> TYPES = new HashMap() {{ 34 | put(CommandEntry.class, -36); 35 | put(ConfigurationEntry.class, -37); 36 | put(KeepAliveEntry.class, -38); 37 | put(InitializeEntry.class, -39); 38 | put(QueryEntry.class, -40); 39 | put(RegisterEntry.class, -41); 40 | put(UnregisterEntry.class, -43); 41 | }}; 42 | 43 | @Override 44 | public void resolve(SerializerRegistry registry) { 45 | for (Map.Entry, Integer> entry : TYPES.entrySet()) { 46 | registry.register(entry.getKey(), entry.getValue()); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/storage/util/TermIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.storage.util; 17 | 18 | import java.util.Map; 19 | import java.util.TreeMap; 20 | 21 | /** 22 | * Log entry term index. 23 | *

    24 | * The term index facilitates storing terms for a group of entries by relating the term only 25 | * to the first offset for all entries in the term. Because terms are monotonically increasing, 26 | * we can assume that if entry {@code n}'s term is {@code t} then entry {@code n + 1}'s term 27 | * will be {@code t} or greater. 28 | *

    29 | * The implementation of the term index uses a {@link TreeMap} to store a map of offsets to 30 | * terms. To look up the term for any given offset, we use {@code map.floorEntry(offset)} 31 | * to loop up the term for the offset. 32 | *

    33 | * This class is thread safe. 34 | * 35 | * @author Jordan Halterman 25 | */ 26 | public class Quorum { 27 | private final int quorum; 28 | private int succeeded = 1; 29 | private int failed; 30 | private Consumer callback; 31 | private boolean complete; 32 | 33 | public Quorum(int quorum, Consumer callback) { 34 | this.quorum = quorum; 35 | this.callback = callback; 36 | } 37 | 38 | private void checkComplete() { 39 | if (!complete && callback != null) { 40 | if (succeeded >= quorum) { 41 | complete = true; 42 | callback.accept(true); 43 | } else if (failed >= quorum) { 44 | complete = true; 45 | callback.accept(false); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Indicates that a call in the quorum succeeded. 52 | */ 53 | public Quorum succeed() { 54 | succeeded++; 55 | checkComplete(); 56 | return this; 57 | } 58 | 59 | /** 60 | * Indicates that a call in the quorum failed. 61 | */ 62 | public Quorum fail() { 63 | failed++; 64 | checkComplete(); 65 | return this; 66 | } 67 | 68 | /** 69 | * Cancels the quorum. Once this method has been called, the quorum will be marked complete and 70 | * the handler will never be called. 71 | */ 72 | public void cancel() { 73 | callback = null; 74 | complete = true; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /server/src/main/java/io/atomix/copycat/server/util/ServerSerialization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 the original author or authors. 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 io.atomix.copycat.server.util; 17 | 18 | import io.atomix.catalyst.serializer.SerializableTypeResolver; 19 | import io.atomix.catalyst.serializer.SerializerRegistry; 20 | import io.atomix.copycat.protocol.Request; 21 | import io.atomix.copycat.server.protocol.*; 22 | import io.atomix.copycat.server.state.ServerMember; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * Request serializable type resolver. 29 | * 30 | * @author , Integer> TYPES = new HashMap() {{ 35 | put(AppendRequest.class, -18); 36 | put(ConfigureRequest.class, -19); 37 | put(InstallRequest.class, -20); 38 | put(JoinRequest.class, -21); 39 | put(LeaveRequest.class, -22); 40 | put(PollRequest.class, -23); 41 | put(ReconfigureRequest.class, -24); 42 | put(VoteRequest.class, -25); 43 | put(AppendResponse.class, -27); 44 | put(ConfigureResponse.class, -28); 45 | put(InstallResponse.class, -29); 46 | put(JoinResponse.class, -30); 47 | put(LeaveResponse.class, -31); 48 | put(PollResponse.class, -32); 49 | put(ReconfigureResponse.class, -33); 50 | put(VoteResponse.class, -34); 51 | put(ServerMember.class, -35); 52 | }}; 53 | 54 | @Override 55 | public void resolve(SerializerRegistry registry) { 56 | for (Map.Entry, Integer> entry : TYPES.entrySet()) { 57 | registry.register(entry.getKey(), entry.getValue()); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/TestStateMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server; 17 | 18 | import io.atomix.copycat.Command; 19 | import io.atomix.copycat.Query; 20 | 21 | /** 22 | * Test state machine. 23 | */ 24 | public class TestStateMachine extends StateMachine { 25 | /** 26 | * Test command. 27 | */ 28 | public static class TestCommand implements Command { 29 | public String value; 30 | 31 | public TestCommand(String value) { 32 | this.value = value; 33 | } 34 | } 35 | 36 | @Override 37 | protected void configure(StateMachineExecutor executor) { 38 | executor.register(TestCommand.class, this::command); 39 | } 40 | 41 | private String command(Commit commit) { 42 | return commit.operation().value; 43 | } 44 | 45 | /** 46 | * Test query. 47 | */ 48 | public static class TestQuery implements Query { 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/Testing.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server; 17 | 18 | import java.util.stream.Stream; 19 | 20 | /** 21 | * Test utilities. 22 | */ 23 | public final class Testing { 24 | static class UncheckedException extends RuntimeException{ 25 | UncheckedException(Throwable cause) { 26 | super(cause); 27 | } 28 | } 29 | 30 | @FunctionalInterface 31 | public interface ThrowableRunnable { 32 | void run() throws Throwable; 33 | } 34 | 35 | public interface TConsumer1 { 36 | void accept(A a) throws Throwable; 37 | } 38 | 39 | public static void withArgs(A[] a, TConsumer1 consumer) throws Throwable { 40 | try { 41 | Stream.of(a).forEach(arg -> uncheck(() -> consumer.accept(arg))); 42 | } catch (UncheckedException e) { 43 | throw e.getCause(); 44 | } 45 | } 46 | 47 | public static void uncheck(ThrowableRunnable runnable) { 48 | try { 49 | runnable.run(); 50 | } catch (Throwable e) { 51 | throw new UncheckedException(e); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/state/LeaderStateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.state; 17 | 18 | import io.atomix.copycat.server.CopycatServer; 19 | import io.atomix.copycat.server.protocol.VoteRequest; 20 | import io.atomix.copycat.server.protocol.VoteResponse; 21 | import org.testng.annotations.BeforeMethod; 22 | import org.testng.annotations.Test; 23 | 24 | /** 25 | * Leader state test. 26 | */ 27 | @Test 28 | public class LeaderStateTest extends AbstractStateTest { 29 | LeaderState state; 30 | 31 | @BeforeMethod 32 | @Override 33 | void beforeMethod() throws Throwable { 34 | super.beforeMethod(); 35 | state = new LeaderState(serverContext); 36 | } 37 | 38 | /** 39 | * Tests that a leader steps down when it receives a higher term. 40 | */ 41 | public void testLeaderStepsDownAndVotesOnHigherTerm() throws Throwable { 42 | runOnServer(() -> { 43 | serverContext.setTerm(1).setLeader(0); 44 | VoteRequest request = VoteRequest.builder() 45 | .withTerm(2) 46 | .withCandidate(members.get(1).hashCode()) 47 | .withLogIndex(11) 48 | .withLogTerm(2) 49 | .build(); 50 | 51 | VoteResponse response = state.vote(request).get(); 52 | 53 | threadAssertEquals(serverContext.getTerm(), 2L); 54 | threadAssertEquals(serverContext.getLastVotedFor(), members.get(1).hashCode()); 55 | threadAssertEquals(response.term(), 2L); 56 | threadAssertTrue(response.voted()); 57 | threadAssertEquals(serverContext.getState(), CopycatServer.State.FOLLOWER); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/storage/AbstractSnapshotStoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | import io.atomix.copycat.server.storage.snapshot.Snapshot; 19 | import io.atomix.copycat.server.storage.snapshot.SnapshotReader; 20 | import io.atomix.copycat.server.storage.snapshot.SnapshotStore; 21 | import io.atomix.copycat.server.storage.snapshot.SnapshotWriter; 22 | import org.testng.annotations.Test; 23 | 24 | import static org.testng.Assert.*; 25 | 26 | /** 27 | * Snapshot store test. 28 | * 29 | * @author Jordan Halterman 29 | */ 30 | @Test 31 | public class MajorCompactionTest extends AbstractLogTest { 32 | 33 | @Override 34 | protected Storage createStorage() { 35 | return tempStorageBuilder() 36 | .withMaxEntriesPerSegment(10) 37 | .build(); 38 | } 39 | 40 | /** 41 | * Tests compacting the log. 42 | */ 43 | public void testMajorCompaction() throws Throwable { 44 | writeEntries(31); 45 | 46 | assertEquals(log.length(), 31L); 47 | 48 | for (long index = 21; index < 28; index++) { 49 | log.release(index); 50 | } 51 | log.commit(31).compactor().minorIndex(31).majorIndex(31); 52 | 53 | CountDownLatch latch = new CountDownLatch(1); 54 | log.compactor().compact(Compaction.MAJOR).thenRun(latch::countDown); 55 | latch.await(); 56 | 57 | assertEquals(log.length(), 31L); 58 | 59 | for (long index = 21; index < 28; index++) { 60 | assertTrue(log.lastIndex() >= index); 61 | assertFalse(log.contains(index)); 62 | try (TestEntry entry = log.get(index)) { 63 | assertNull(entry); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Writes a set of session entries to the log. 70 | */ 71 | private void writeEntries(int entries) { 72 | for (int i = 0; i < entries; i++) { 73 | try (TestEntry entry = log.create(TestEntry.class)) { 74 | entry.setTerm(1); 75 | if (entry.getIndex() % 2 == 0) { 76 | entry.setCompactionMode(Compaction.Mode.SEQUENTIAL); 77 | } else { 78 | entry.setCompactionMode(Compaction.Mode.QUORUM); 79 | } 80 | log.append(entry); 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/storage/MappedLogTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | import org.testng.annotations.Factory; 19 | import org.testng.annotations.Test; 20 | 21 | /** 22 | * Memory mapped file log test. 23 | * 24 | * @author Jordan Halterman 29 | */ 30 | @Test 31 | public class MinorCompactionTest extends AbstractLogTest { 32 | 33 | @Override 34 | protected Storage createStorage() { 35 | return tempStorageBuilder() 36 | .withMaxEntriesPerSegment(10) 37 | .build(); 38 | } 39 | 40 | /** 41 | * Tests compacting the log. 42 | */ 43 | public void testMinorCompaction() throws Throwable { 44 | writeEntries(31); 45 | 46 | assertEquals(log.length(), 31L); 47 | 48 | for (long index = 21; index < 28; index++) { 49 | log.release(index); 50 | } 51 | log.commit(31).compactor().minorIndex(31); 52 | 53 | CountDownLatch latch = new CountDownLatch(1); 54 | log.compactor().compact(Compaction.MINOR).thenRun(latch::countDown); 55 | latch.await(); 56 | 57 | assertEquals(log.length(), 31L); 58 | 59 | for (long index = 21; index < 28; index++) { 60 | assertTrue(log.lastIndex() >= index); 61 | if (index % 2 != 0) { 62 | assertFalse(log.contains(index)); 63 | try (TestEntry entry = log.get(index)) { 64 | assertNull(entry); 65 | } 66 | } else { 67 | assertTrue(log.contains(index)); 68 | try (TestEntry entry = log.get(index)) { 69 | assertNotNull(entry); 70 | } 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Writes a set of session entries to the log. 77 | */ 78 | private void writeEntries(int entries) { 79 | for (int i = 0; i < entries; i++) { 80 | try (TestEntry entry = log.create(TestEntry.class)) { 81 | entry.setTerm(1); 82 | entry.setPadding(1); 83 | if (entry.getIndex() % 2 == 0) { 84 | entry.setCompactionMode(Compaction.Mode.SEQUENTIAL); 85 | } else { 86 | entry.setCompactionMode(Compaction.Mode.QUORUM); 87 | } 88 | log.append(entry); 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/storage/OffsetPredicateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.storage; 17 | 18 | import io.atomix.copycat.server.storage.util.OffsetPredicate; 19 | import org.testng.annotations.Test; 20 | 21 | import static org.testng.Assert.*; 22 | 23 | /** 24 | * Offset cleaner test. 25 | * 26 | * @author Jordan Halterman 29 | */ 30 | public class TestEntry extends Entry { 31 | /** Padding to vary the stored size of an entry */ 32 | private Compaction.Mode compaction = Compaction.Mode.QUORUM; 33 | private int paddingSize; 34 | private byte[] padding = new byte[0]; 35 | 36 | public TestEntry() { 37 | } 38 | 39 | public TestEntry(ReferenceManager> referenceManager) { 40 | super(referenceManager); 41 | } 42 | 43 | @Override 44 | public void readObject(BufferInput buffer, Serializer serializer) { 45 | setTerm(buffer.readLong()); 46 | compaction = Compaction.Mode.values()[buffer.readByte()]; 47 | paddingSize = buffer.readInt(); 48 | padding = new byte[paddingSize]; 49 | buffer.read(padding); 50 | } 51 | 52 | @Override 53 | public void writeObject(BufferOutput buffer, Serializer serializer) { 54 | buffer.writeLong(getTerm()).writeByte(compaction.ordinal()).writeInt(paddingSize).write(padding); 55 | } 56 | 57 | public byte[] getPadding() { 58 | return padding; 59 | } 60 | 61 | public void setPadding(int paddingSize) { 62 | this.paddingSize = paddingSize; 63 | this.padding = new byte[paddingSize]; 64 | } 65 | 66 | @Override 67 | public Compaction.Mode getCompactionMode() { 68 | return compaction; 69 | } 70 | 71 | public TestEntry setCompactionMode(Compaction.Mode mode) { 72 | this.compaction = mode; 73 | return this; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return String.format("%s[index=%d, term=%d, compaction=%s]", getClass().getSimpleName(), getIndex(), getTerm(), compaction); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /server/src/test/java/io/atomix/copycat/server/util/QuorumTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 the original author or authors. 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 io.atomix.copycat.server.util; 17 | 18 | import org.testng.annotations.BeforeMethod; 19 | import org.testng.annotations.Test; 20 | 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | import static org.testng.Assert.assertEquals; 24 | 25 | /** 26 | * Quorum test. 27 | * 28 | * @author Jordan Halterman 29 | */ 30 | @Test 31 | public class QuorumTest { 32 | Quorum quorum; 33 | AtomicInteger callbackResult; 34 | 35 | @BeforeMethod 36 | protected void beforeMethod() { 37 | callbackResult = new AtomicInteger(); 38 | quorum = new Quorum(3, r -> { 39 | callbackResult.set(r ? 1 : 2); 40 | }); 41 | } 42 | 43 | /** 44 | * Tests a successful quorum. 45 | */ 46 | public void testQuorumSucceed() { 47 | quorum.succeed(); 48 | quorum.fail(); 49 | assertEquals(callbackResult.get(), 0); 50 | quorum.fail(); 51 | quorum.succeed(); 52 | assertEquals(callbackResult.get(), 1); 53 | } 54 | 55 | /** 56 | * Tests a failed quorum. 57 | */ 58 | public void testQuorumFail() { 59 | quorum.fail(); 60 | quorum.fail(); 61 | quorum.succeed(); 62 | assertEquals(callbackResult.get(), 0); 63 | quorum.fail(); 64 | assertEquals(callbackResult.get(), 2); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /server/src/test/resources/META-INF/services/io.atomix.catalyst.serializer.CatalystSerializable: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015 the original author or authors. 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 | io.atomix.copycat.server.storage.TestEntry 17 | io.atomix.copycat.server.storage.MetaStoreTest$TestMember 18 | -------------------------------------------------------------------------------- /server/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | target/fuzz-logs/test.log 19 | 20 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 21 | 22 | 23 | 24 | 25 | 26 | INFO 27 | 28 | 29 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------