From 634094f766e76c627d71aeee897a01b8fb86e8ae Mon Sep 17 00:00:00 2001 From: Raycho Mukelov Date: Thu, 31 Aug 2023 23:52:52 +0300 Subject: [PATCH] Add basic Raft Cluster test --- .gitignore | 3 +- {tests => misc}/test_macro.nim | 12 ++++---- {tests => misc}/vm_compile_info.nim | 15 +++++----- raft/protocol.nim | 7 +---- raft/raft_api.nim | 15 +++++++--- raft/types.nim | 34 +++++++++++++---------- tests/all_tests.nim | 7 +++-- tests/basic_cluster.nim | 43 +++++++++++++++++++++++++++++ tests/test_basic_cluster.nim | 28 +++++++++++++++++++ 9 files changed, 123 insertions(+), 41 deletions(-) rename {tests => misc}/test_macro.nim (89%) rename {tests => misc}/vm_compile_info.nim (62%) create mode 100644 tests/basic_cluster.nim create mode 100644 tests/test_basic_cluster.nim diff --git a/.gitignore b/.gitignore index 1c3fa9c..52100bb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ *.exe *.out nimcache/ -build/ \ No newline at end of file +build/ +nimbledeps/ \ No newline at end of file diff --git a/tests/test_macro.nim b/misc/test_macro.nim similarity index 89% rename from tests/test_macro.nim rename to misc/test_macro.nim index 2403105..2cb9900 100644 --- a/tests/test_macro.nim +++ b/misc/test_macro.nim @@ -1,9 +1,11 @@ -# Nimbus -# Copyright (c) 2018-2019 Status Research & Development GmbH +# nim-raft +# Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -# at your option. This file may not be copied, modified, or distributed except according to those terms. +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. import std/times import vm_compile_info diff --git a/tests/vm_compile_info.nim b/misc/vm_compile_info.nim similarity index 62% rename from tests/vm_compile_info.nim rename to misc/vm_compile_info.nim index 262491a..6463d21 100644 --- a/tests/vm_compile_info.nim +++ b/misc/vm_compile_info.nim @@ -1,12 +1,11 @@ -# Nimbus -# Copyright (c) 2018 Status Research & Development GmbH +# nim-raft +# Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or -# http://www.apache.org/licenses/LICENSE-2.0) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or -# http://opensource.org/licenses/MIT) -# at your option. This file may not be copied, modified, or distributed except -# according to those terms. +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. func vmName(): string = when defined(evmc_enabled): diff --git a/raft/protocol.nim b/raft/protocol.nim index 595beca..c288ae9 100644 --- a/raft/protocol.nim +++ b/raft/protocol.nim @@ -23,12 +23,6 @@ type rmreSuccess = 0, rmreFail = 1 - RaftMessageResponseBase* = ref object of RootObj - msgId*: RaftMessageId # Original Message ID - senderId*: RaftNodeId # Sender Raft Node ID - respondentId: RaftNodeId # Responding RaftNodeId - senderTerm*: RaftNodeTerm # Sender Raft Node Term - RaftMessageRequestVote* = ref object of RaftMessageBase lastLogTerm*: RaftNodeTerm lastLogIndex*: RaftLogIndex @@ -59,6 +53,7 @@ type RaftNodeClientRequest*[SmCommandType] = ref object op*: RaftNodeClientRequestOps + nodeId*: RaftNodeId payload*: Option[SmCommandType] # Optional RaftMessagePayload carrying a Log Entry RaftNodeClientResponse*[SmStateType] = ref object diff --git a/raft/raft_api.nim b/raft/raft_api.nim index 2db46ff..61afe44 100644 --- a/raft/raft_api.nim +++ b/raft/raft_api.nim @@ -17,16 +17,23 @@ proc RaftNodeSmInit[SmCommandType, SmStateType](stateMachine: var RaftNodeStateM # Raft Node Public API procedures / functions proc RaftNodeCreateNew*[SmCommandType, SmStateType]( # Create New Raft Node - id: RaftNodeId, peers: RaftNodePeers, - persistentStorage: RaftNodePersistentStorage, + id: RaftNodeId, peersIds: seq[RaftNodeId], + # persistentStorage: RaftNodePersistentStorage, msgSendCallback: RaftMessageSendCallback): RaftNode[SmCommandType, SmStateType] = var - sm = RaftNodeStateMachine[SmCommandType, SmStateType] + sm: RaftNodeStateMachine[SmCommandType, SmStateType] + peers: RaftNodePeers + RaftNodeSmInit[SmCommandType, SmStateType](sm) + + for peerId in peersIds: + peers.add(RaftNodePeer(id: peerId, nextIndex: 0, matchIndex: 0, hasVoted: false, canVote: true)) + result = RaftNode[SmCommandType, SmStateType]( - id: id, state: rnsFollower, currentTerm: 0, votedFor: nil, peers: peers, commitIndex: 0, lastApplied: 0, + id: id, state: rnsFollower, currentTerm: 0, peers: peers, commitIndex: 0, lastApplied: 0, stateMachine: sm, msgSendCallback: msgSendCallback ) + initLock(result.raftStateMutex) proc RaftNodeLoad*[SmCommandType, SmStateType]( persistentStorage: RaftNodePersistentStorage, # Load Raft Node From Storage diff --git a/raft/types.nim b/raft/types.nim index 80081f7..2484110 100644 --- a/raft/types.nim +++ b/raft/types.nim @@ -14,7 +14,7 @@ import options import stew/results import uuids -export results, options +export results, options, locks, uuids type @@ -24,7 +24,6 @@ type rnsCandidate = 2 rnsLeader = 3 - UUID = object RaftNodeId* = UUID # uuid4 uniquely identifying every Raft Node RaftNodeTerm* = uint64 # Raft Node Term Type RaftLogIndex* = uint64 # Raft Node Log Index Type @@ -71,10 +70,17 @@ type RaftMessageBase* = ref object of RootObj # Base Type for Raft Protocol Messages msgId*: RaftMessageId # Message UUID senderId*: RaftNodeId # Sender Raft Node ID + receiverId*: RaftNodeId # Receiver Raft Node ID senderTerm*: RaftNodeTerm # Sender Raft Node Term - RaftMessageSendCallback* = proc (recipient: RaftNodeId, raftMessage: RaftMessageBase) {.nimcall, gcsafe.} # Callback for Sending Raft Node Messages - # out of this Raft Node. + RaftMessageResponseBase* = ref object of RootObj + msgId*: RaftMessageId # Original Message ID + senderId*: RaftNodeId # Sender Raft Node ID + respondentId: RaftNodeId # Responding RaftNodeId + senderTerm*: RaftNodeTerm # Sender Raft Node Term + + RaftMessageSendCallback* = proc (raftMessage: RaftMessageBase): RaftMessageResponseBase {.gcsafe.} # Callback for Sending Raft Node Messages + # out of this Raft Node. # For later use when adding/removing new nodes (dynamic configuration chganges) RaftNodeConfiguration* = object @@ -120,7 +126,7 @@ type appendEntriesTimer: RaftTimer # Mtx definition(s) go here - raftStateMutex: Lock + raftStateMutex*: Lock # Modules (Algos) consensusModule: RaftConsensusModule[SmCommandType, SmStateType] @@ -128,22 +134,22 @@ type membershipChangeModule: RaftMembershipChangeModule[SmCommandType, SmStateType] # Misc - msgSendCallback: RaftMessageSendCallback + msgSendCallback*: RaftMessageSendCallback persistentStorage: RaftNodePersistentStorage[SmCommandType, SmStateType] # Persistent state - id: RaftNodeId # This Raft Node ID - state: RaftNodeState # This Raft Node State - currentTerm: RaftNodeTerm # Latest term this Raft Node has seen (initialized to 0 on first boot, increases monotonically) - votedFor: RaftNodeId # Candidate RaftNodeId that received vote in current term (or nil/zero if none), + id*: RaftNodeId # This Raft Node ID + state*: RaftNodeState # This Raft Node State + currentTerm*: RaftNodeTerm # Latest term this Raft Node has seen (initialized to 0 on first boot, increases monotonically) + votedFor*: RaftNodeId # Candidate RaftNodeId that received vote in current term (or nil/zero if none), # also used to redirect Client Requests in case this Raft Node is not the leader log: RaftNodeLog[SmCommandType] # This Raft Node Log - stateMachine: RaftNodeStateMachine[SmCommandType, SmStateType] # Not sure for now putting it here. I assume that persisting the State Machine's + stateMachine*: RaftNodeStateMachine[SmCommandType, SmStateType] # Not sure for now putting it here. I assume that persisting the State Machine's # state is enough to consider it 'persisted' - peers: RaftNodePeers # This Raft Node Peers IDs. I am not sure if this must be persistent or volatile but making it persistent + peers*: RaftNodePeers # This Raft Node Peers IDs. I am not sure if this must be persistent or volatile but making it persistent # makes sense for the moment # Volatile state - commitIndex: RaftLogIndex # Index of highest log entry known to be committed (initialized to 0, increases monotonically) - lastApplied: RaftLogIndex # Index of highest log entry applied to state machine (initialized to 0, increases monotonically) + commitIndex*: RaftLogIndex # Index of highest log entry known to be committed (initialized to 0, increases monotonically) + lastApplied*: RaftLogIndex # Index of highest log entry applied to state machine (initialized to 0, increases monotonically) currentLeaderId: RaftNodeId # The ID of the cirrent leader Raft Node or 0/nil if None is leader (election is in progress etc.) \ No newline at end of file diff --git a/tests/all_tests.nim b/tests/all_tests.nim index a050444..16ea541 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -7,10 +7,11 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import test_macro +import ../misc/test_macro -{. warning[UnusedImport]: off .} +{. warning[UnusedImport]:off .} cliBuilder: - import ./test_basic_state_machine, + import ./test_basic_cluster, + ./test_basic_state_machine, ./test_basic_timers \ No newline at end of file diff --git a/tests/basic_cluster.nim b/tests/basic_cluster.nim new file mode 100644 index 0000000..6063c81 --- /dev/null +++ b/tests/basic_cluster.nim @@ -0,0 +1,43 @@ +# nim-raft +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import basic_timers +import basic_state_machine +import ../raft/raft_api + +type + BasicRaftNode* = RaftNode[SmCommand, SmState] + + BasicRaftCluster* = ref object + nodes*: seq[BasicRaftNode] + +proc BasicRaftClusterRaftMessageSendCallbackCreate(cluster: BasicRaftCluster): RaftMessageSendCallback = + proc (msg: RaftMessageBase): RaftMessageResponseBase {.closure.} = + var + nodeIdx: int = -1 + + for i in 0..cluster.nodes.len: + if cluster.nodes[i].id == msg.receiverId: + nodeIdx = i + break + + cluster.nodes[nodeIdx].RaftNodeMessageDeliver(msg) + +proc BasicRaftClusterClientRequest*(cluster: BasicRaftCluster, req: RaftNodeClientRequest): RaftNodeClientResponse = + discard + +proc BasicRaftClusterInit*(nodesIds: seq[RaftNodeId]): BasicRaftCluster = + new(result) + for nodeId in nodesIds: + var + peersIds = nodesIds + + peersIds.del(peersIds.find(nodeId)) + result.nodes.add(RaftNodeCreateNew[SmCommand, SmState](nodeId, peersIds, BasicRaftClusterRaftMessageSendCallbackCreate(result))) + diff --git a/tests/test_basic_cluster.nim b/tests/test_basic_cluster.nim new file mode 100644 index 0000000..ad9e4bb --- /dev/null +++ b/tests/test_basic_cluster.nim @@ -0,0 +1,28 @@ +# nim-raft +# Copyright (c) 2023 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import unittest2 +import basic_cluster +import ../raft/types + +proc basicClusterMain*() = + var + cluster: BasicRaftCluster + nodesIds = newSeq[RaftNodeId](5) + + suite "Basic Raft Cluster Tests": + + test "Test Basic Raft Cluster Init (5 nodes)": + for i in 0..4: + nodesIds[i] = genUUID() + + cluster = BasicRaftClusterInit(nodesIds) + +if isMainModule: + basicClusterMain() \ No newline at end of file