Skip to content

Commit

Permalink
Byraft v0.1.0
Browse files Browse the repository at this point in the history
* Feat: leader election
  • Loading branch information
taekop committed Aug 1, 2022
1 parent bb1da41 commit 0f3c22c
Show file tree
Hide file tree
Showing 23 changed files with 998 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
tmp
12 changes: 12 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
source 'https://rubygems.org'

gemspec

group :development, :test do
gem 'pry'
end

group :test do
gem 'rspec'
gem 'timecop'
end
48 changes: 48 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
PATH
remote: .
specs:
byraft (0.1.0)
grpc (~> 1)

GEM
remote: https://rubygems.org/
specs:
coderay (1.1.3)
diff-lcs (1.5.0)
google-protobuf (3.21.2)
googleapis-common-protos-types (1.3.2)
google-protobuf (~> 3.14)
grpc (1.47.0)
google-protobuf (~> 3.19)
googleapis-common-protos-types (~> 1.0)
method_source (1.0.0)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
timecop (0.9.5)

PLATFORMS
arm64-darwin-21
ruby

DEPENDENCIES
byraft!
pry
rspec
timecop

BUNDLED WITH
2.3.7
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,33 @@
# byraft
# Byraft

Byraft is an implementation of Raft consensus algorithm in Ruby using gRPC.

## Example

Three nodes communicates each other with the following configuration.
- `election timeout` between 1 and 2 sec
- `update period` as 0.1 sec
- `verbose`
- Nodes
- #1 on localhost:50051
- #2 on localhost:50052
- #3 on localhost:50053

Run examples in different terminal tabs.

```shell
bin/example 1 # terminal 1
bin/example 2 # terminal 2
bin/example 3 # terminal 3
```

## Test

```shell
# rspec
bin/test
```

## Reference

[paper](https://raft.github.io/raft.pdf)
61 changes: 61 additions & 0 deletions bin/byraft
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env ruby

require 'optparse'
require 'byraft'

params = {}
OptionParser.new do |parser|
parser.banner = <<-BANNER
Run byraft application
Usage: byraft [options]
-h --help Print help
-i <id> Configure id for current node
-p <port> Configure port for current node
-n <id>=<address> Configure node id and address
-e <range> Configure election timeout in sec (ex) 0.1, 0.1:0.3
-u Configure update period in sec
-v Configure verbose option
Option:
BANNER
parser.on("-h", "--help") do
puts parser
exit
end
parser.on("-i", "--id <id>", String) do |id|
params[:id] = id
end
parser.on("-p", "--port <port>", Integer) do |port|
params[:port] = port
end
parser.on("-n", "--node <id>=<address>", String) do |str|
id, address = str.split('=')
params[:node] ||= {}
params[:node][id] = address
end
parser.on("-e", "--e <range>", String) do |str|
s, e = str.split(':')
e = s unless e
s, e = s.to_f, e.to_f
params[:election_timeout] = s..e
end
parser.on("-u", "--update <period>", Float) do |p|
params[:update_period] = p
end
parser.on("-v", "--verbose", TrueClass) do |v|
params[:verbose] = v
end
end.parse!

unless params[:id]
puts "Please configure id (ex) -i 1"
exit
end

unless params[:node]
puts "Please configure nodes (ex) -n 1=localhost:50051"
exit
end

id, port, nodes, opts = params[:id], params[:port], params[:node], params.except(:id, :port, :node)
Byraft.start(id, port, nodes, **opts)
Byraft.join
19 changes: 19 additions & 0 deletions bin/example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

port1=50051
port2=50052
port3=50053
if [ -z ${1+x} ]; then echo "Please set id from 1 to 3"; fi
script_dir=$(dirname $0)
id=$1
case ${id} in
1)
port=${port1}
;;
2)
port=${port2}
;;
3)
port=${port3}
esac
bundle exec ${script_dir}/byraft -i ${id} -p ${port} -n 1=localhost:${port1} -n 2=localhost:${port2} -n 3=localhost:${port3} -v
3 changes: 3 additions & 0 deletions bin/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

bundle exec rspec
15 changes: 15 additions & 0 deletions byraft.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Gem::Specification.new do |s|
s.name = 'byraft'
s.version = '0.1.0'
s.platform = Gem::Platform::RUBY
s.authors = ["taekop"]
s.email = ["taekop@naver.com"]
s.homepage = 'http://github.com/taekop/byraft'
s.summary = "Raft Implemention in Ruby"
s.description = s.summary
s.license = 'MIT'

s.add_dependency 'grpc', '~> 1'

s.files = ["lib/byraft.rb"]
end
37 changes: 37 additions & 0 deletions lib/byraft.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'byraft/node'

module Byraft
# @param id [String]
# @param nodes [Hash]: id as key, and address as value
#
# @example
#
# Byraft.start('1', 50051, { 1 => '0.0.0.0:50051', 2 => '0.0.0.0:50052', 3 => '0.0.0.0:50053' })
def self.start(id, port, nodes, **opts)
node = Node.new(id, nodes, **opts)
address = "localhost:#{port}"
@server_thread = Thread.new do
puts "Node##{id} running..." if opts[:verbose]
s = ::GRPC::RpcServer.new
s.add_http2_port(address, :this_port_is_insecure)
s.handle(node)
s.run_till_terminated_or_interrupted(['INT', 'TERM'])
end
@ping_thread = Thread.new do
loop do
sleep(node.update_period)
node.update
end
end
end

def self.join
@server_thread.join
@ping_thread.kill
end

def self.stop
@server_thread.kill
@ping_thread.kill
end
end
45 changes: 45 additions & 0 deletions lib/byraft/grpc/byraft_pb.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'google/protobuf'

Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "byraft.Entry" do
optional :index, :int32, 1
optional :term, :int32, 2
optional :command, :string, 3
end

add_message "byraft.AppendEntriesRequest" do
optional :term, :int32, 1
optional :leader_id, :string, 2
optional :prev_log_index, :int32, 3
optional :prev_log_term, :int32, 4
repeated :entries, :message, 5, "byraft.Entry"
optional :leader_commit, :int32, 6
end

add_message "byraft.AppendEntriesResponse" do
optional :term, :int32, 1
optional :success, :bool, 2
end

add_message "byraft.RequestVoteRequest" do
optional :term, :int32, 1
optional :candidate_id, :string, 2
optional :last_log_index, :int32, 3
optional :last_log_term, :int32, 4
end

add_message "byraft.RequestVoteResponse" do
optional :term, :int32, 1
optional :vote_granted, :bool, 2
end
end

module Byraft
module GRPC
Entry = Google::Protobuf::DescriptorPool.generated_pool.lookup("byraft.Entry").msgclass
AppendEntriesRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("byraft.AppendEntriesRequest").msgclass
AppendEntriesResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("byraft.AppendEntriesResponse").msgclass
RequestVoteRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("byraft.RequestVoteRequest").msgclass
RequestVoteResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("byraft.RequestVoteResponse").msgclass
end
end
25 changes: 25 additions & 0 deletions lib/byraft/grpc/byraft_services_pb.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'grpc'

require 'byraft/grpc/byraft_pb'

module Byraft
module GRPC
# Interface exported by the server.
module Byraft
def self.included klass
klass.class_eval do
include ::GRPC::GenericService

klass.marshal_class_method = :encode
klass.unmarshal_class_method = :decode
klass.service_name = 'byraft.RaftNode'

klass.rpc :AppendEntries, AppendEntriesRequest, AppendEntriesResponse
klass.rpc :RequestVote, RequestVoteRequest, RequestVoteResponse
end
end
end

Stub = Class.new.include(Byraft).rpc_stub_class
end
end
Loading

0 comments on commit 0f3c22c

Please sign in to comment.