rswim 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +79 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/run_node +30 -0
- data/bin/setup +8 -0
- data/lib/rswim.rb +33 -0
- data/lib/rswim/agent.rb +69 -0
- data/lib/rswim/directory.rb +27 -0
- data/lib/rswim/integration/deserializer.rb +51 -0
- data/lib/rswim/integration/serializer.rb +29 -0
- data/lib/rswim/integration/udp/node.rb +61 -0
- data/lib/rswim/logger.rb +31 -0
- data/lib/rswim/member/ack_responder.rb +22 -0
- data/lib/rswim/member/base.rb +11 -0
- data/lib/rswim/member/forwarding_state/base.rb +33 -0
- data/lib/rswim/member/forwarding_state/forwarding_ack.rb +26 -0
- data/lib/rswim/member/forwarding_state/ready.rb +24 -0
- data/lib/rswim/member/health_state/alive.rb +31 -0
- data/lib/rswim/member/health_state/base.rb +71 -0
- data/lib/rswim/member/health_state/confirmed.rb +31 -0
- data/lib/rswim/member/health_state/suspected.rb +32 -0
- data/lib/rswim/member/me.rb +45 -0
- data/lib/rswim/member/peer.rb +85 -0
- data/lib/rswim/member/transmission_state/awaiting_ack.rb +38 -0
- data/lib/rswim/member/transmission_state/base.rb +48 -0
- data/lib/rswim/member/transmission_state/off.rb +21 -0
- data/lib/rswim/member/transmission_state/ready.rb +27 -0
- data/lib/rswim/member/transmission_state/sending_ping.rb +30 -0
- data/lib/rswim/member/transmission_state/sending_ping_request.rb +31 -0
- data/lib/rswim/member_pool.rb +114 -0
- data/lib/rswim/message.rb +18 -0
- data/lib/rswim/node.rb +50 -0
- data/lib/rswim/pipe.rb +32 -0
- data/lib/rswim/protocol_state.rb +65 -0
- data/lib/rswim/status_report.rb +29 -0
- data/lib/rswim/update_entry.rb +17 -0
- data/lib/rswim/version.rb +5 -0
- data/log/.keep +0 -0
- data/rswim.gemspec +37 -0
- data/tmp/.keep +0 -0
- metadata +195 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Integration
|
5
|
+
class Deserializer
|
6
|
+
def initialize(directory, my_id)
|
7
|
+
@directory = directory
|
8
|
+
@my_id = my_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def deserialize(sender_host, wire_message)
|
12
|
+
l, *ls = wire_message.split("\n")
|
13
|
+
# First line is
|
14
|
+
# type [target_id]
|
15
|
+
ary = l.strip.split(' ')
|
16
|
+
type = ary[0].gsub(/-/, '_').to_sym
|
17
|
+
payload = type == :ping_req ? { target_id: @directory.id(ary[1]) } : {}
|
18
|
+
payload[:updates] = parse_updates(ls)
|
19
|
+
from = @directory.id(sender_host)
|
20
|
+
RSwim::Message.new(@my_id, from, type, payload)
|
21
|
+
rescue StandardError => e
|
22
|
+
logger.debug("Failed to parse line `#{l}`: #{e}")
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def logger
|
29
|
+
@_logger ||= begin
|
30
|
+
RSwim::Logger.new(self.class, STDERR)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parse_updates(lines)
|
37
|
+
lines.map do |l|
|
38
|
+
begin
|
39
|
+
# host status incarnation_number
|
40
|
+
host, status, incarnation_number = l.strip.split(' ')
|
41
|
+
id = @directory.id(host)
|
42
|
+
UpdateEntry.new(id, status.to_sym, incarnation_number.to_i)
|
43
|
+
rescue StandardError => e
|
44
|
+
logger.debug("Failed to parse line `#{l}`: #{e}")
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end.tap(&:compact!)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Integration
|
5
|
+
class Serializer
|
6
|
+
def initialize(directory)
|
7
|
+
@directory = directory
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(message)
|
11
|
+
l1 = message.type.to_s.gsub(/_/, '-')
|
12
|
+
l1 << " #{@directory.host(message.payload[:target_id])}" if message.type == :ping_req
|
13
|
+
message.payload[:updates].to_a.each do |update|
|
14
|
+
# host status incarnation_number
|
15
|
+
l1 << "\n#{@directory.host(update.member_id)} #{update.status} #{update.incarnation_number}"
|
16
|
+
end
|
17
|
+
l1
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def logger
|
23
|
+
@_logger ||= begin
|
24
|
+
RSwim::Logger.new(self.class, STDERR)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module RSwim
|
6
|
+
module Integration
|
7
|
+
module Udp
|
8
|
+
class Node < RSwim::Node
|
9
|
+
def initialize(my_host, seed_hosts, port)
|
10
|
+
my_host ||= Socket.ip_address_list.find { |x| x.ipv4_private? }.ip_address
|
11
|
+
super(my_host, seed_hosts)
|
12
|
+
@port = port
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def before_start
|
18
|
+
@in_s = UDPSocket.new
|
19
|
+
@out_s = UDPSocket.new
|
20
|
+
@in_s.bind(@my_host, @port)
|
21
|
+
logger.info "node listening on UDP port #{@port}"
|
22
|
+
Thread.new { receive }.tap { |t| t.abort_on_exception = true }
|
23
|
+
Thread.new { send }.tap { |t| t.abort_on_exception = true }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def send
|
29
|
+
loop do
|
30
|
+
begin
|
31
|
+
message = @pipe.q_out.pop
|
32
|
+
wire_message = @serializer.serialize(message)
|
33
|
+
host = @directory.host(message.to)
|
34
|
+
logger.debug "about to send message to #{host}"
|
35
|
+
@out_s.send(wire_message, 0, host, @port)
|
36
|
+
rescue StandardError => e
|
37
|
+
logger.debug("Error while sending: #{e}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
logger.info 'node no longer receiving'
|
41
|
+
end
|
42
|
+
|
43
|
+
def receive
|
44
|
+
loop do
|
45
|
+
begin
|
46
|
+
logger.debug 'about to recieve message'
|
47
|
+
text, sender = @in_s.recvfrom(10_000)
|
48
|
+
|
49
|
+
message = @deserializer.deserialize(sender[3], text)
|
50
|
+
logger.debug "received #{message}"
|
51
|
+
@pipe.q_in << message
|
52
|
+
rescue StandardError => e
|
53
|
+
logger.debug("Error while receiving: #{e}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
logger.info 'node no longer receiving'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/rswim/logger.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module RSwim
|
2
|
+
class Logger < ::Logger
|
3
|
+
def initialize(klass, *args, **options)
|
4
|
+
options[:formatter] = LeFormatter.new(klass)
|
5
|
+
super(*args, **options)
|
6
|
+
self.level=Logger.level
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def level=(severity)
|
11
|
+
@level = severity
|
12
|
+
end
|
13
|
+
|
14
|
+
def level
|
15
|
+
@level ||= ::Logger::INFO
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class LeFormatter < ::Logger::Formatter
|
20
|
+
def initialize(klass)
|
21
|
+
super()
|
22
|
+
@klass = klass
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(severity, timestamp, progname, msg)
|
26
|
+
m = String === msg ? msg : msg.inspect
|
27
|
+
"#{timestamp} | #{severity} | #{@klass} | #{m}\n"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
class AckResponder
|
6
|
+
def initialize(id)
|
7
|
+
@id = id
|
8
|
+
@pending = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def schedule_ack(member_id)
|
12
|
+
@pending << Message.new(member_id, @id, :ack)
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepare_output
|
16
|
+
result = @pending
|
17
|
+
@pending = []
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module ForwardingState
|
6
|
+
class Base
|
7
|
+
def initialize(id, node_member_id)
|
8
|
+
@id = id
|
9
|
+
@node_member_id = node_member_id
|
10
|
+
logger.debug("Member with id #{id} entered new state: #{self.class}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def forward_ack_to_member; end
|
14
|
+
|
15
|
+
def advance(_elapsed_seconds)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def prepare_output
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def logger
|
26
|
+
@_logger ||= begin
|
27
|
+
RSwim::Logger.new("Node #{@node_member_id}", STDERR)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module ForwardingState
|
6
|
+
class ForwardingAck < Base
|
7
|
+
def initialize(id, node_member_id)
|
8
|
+
super
|
9
|
+
@done = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def prepare_output
|
13
|
+
@done = true
|
14
|
+
message = Message.new(@id, @node_member_id, :ack)
|
15
|
+
[message]
|
16
|
+
end
|
17
|
+
|
18
|
+
def advance(elapsed_seconds)
|
19
|
+
if @done then Ready.new(@id, @node_member_id)
|
20
|
+
else self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module ForwardingState
|
6
|
+
class Ready < Base
|
7
|
+
def initialize(id, node_member_id)
|
8
|
+
super
|
9
|
+
@activated = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def forward_ack_to_member
|
13
|
+
@activated = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance(_elapsed_seconds)
|
17
|
+
if @activated then ForwardingAck.new(@id, @node_member_id)
|
18
|
+
else self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module HealthState
|
6
|
+
class Alive < Base
|
7
|
+
def initialize(id, member_pool, update_entry = UpdateEntry.new(id, :alive, 0, 0))
|
8
|
+
super
|
9
|
+
@failed_to_reply = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def advance(_elapsed_seconds)
|
13
|
+
if @failed_to_reply
|
14
|
+
ue = UpdateEntry.new(@id, :suspected, @update_entry.incarnation_number, -1)
|
15
|
+
Suspected.new(@id, @member_pool, ue, true)
|
16
|
+
else
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def member_failed_to_reply
|
22
|
+
@failed_to_reply = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def can_be_pinged?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module HealthState
|
6
|
+
class Base
|
7
|
+
attr_reader :update_entry
|
8
|
+
|
9
|
+
def initialize(id, member_pool, update_entry)
|
10
|
+
@member_pool = member_pool
|
11
|
+
@id = id
|
12
|
+
@update_entry = update_entry
|
13
|
+
logger.debug("Member with id #{id} entered new state: #{self.class}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance(_elapsed_seconds)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_suspicion(status, incarnation_number)
|
21
|
+
incarnation_number ||= @update_entry.incarnation_number
|
22
|
+
s0 = @update_entry.status
|
23
|
+
i0 = @update_entry.incarnation_number
|
24
|
+
case status
|
25
|
+
when :confirmed
|
26
|
+
if (s0 == :confirmed)
|
27
|
+
self
|
28
|
+
else
|
29
|
+
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
30
|
+
Confirmed.new(@id, @member_pool, ue)
|
31
|
+
end
|
32
|
+
when :suspected
|
33
|
+
if (s0 == :suspected && incarnation_number > i0) ||
|
34
|
+
(s0 == :alive && incarnation_number >= i0)
|
35
|
+
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
36
|
+
Suspected.new(@id, @member_pool, ue, false)
|
37
|
+
else
|
38
|
+
self
|
39
|
+
end
|
40
|
+
when :alive
|
41
|
+
if (s0 == :suspected && incarnation_number > i0) ||
|
42
|
+
(s0 == :alive && incarnation_number > i0)
|
43
|
+
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
44
|
+
Alive.new(@id, @member_pool, ue)
|
45
|
+
else
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def member_failed_to_reply; end
|
52
|
+
|
53
|
+
def increment_propagation_count
|
54
|
+
@update_entry.increment_propagation_count
|
55
|
+
end
|
56
|
+
|
57
|
+
def can_be_pinged?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def logger
|
64
|
+
@_logger ||= begin
|
65
|
+
RSwim::Logger.new("unknown node", STDERR)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module HealthState
|
6
|
+
class Confirmed < Base
|
7
|
+
def initialize(id, member_pool, update_entry)
|
8
|
+
super
|
9
|
+
@member_halted = false
|
10
|
+
@member_removed = false
|
11
|
+
@life_time_seconds = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def advance(elapsed_seconds)
|
15
|
+
@life_time_seconds += elapsed_seconds
|
16
|
+
unless @member_halted
|
17
|
+
@member_pool.halt_member(@id)
|
18
|
+
@member_halted = true
|
19
|
+
end
|
20
|
+
|
21
|
+
if !@member_removed && @life_time_seconds > 10
|
22
|
+
@member_pool.remove_member(@id)
|
23
|
+
@member_removed = true
|
24
|
+
end
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module HealthState
|
6
|
+
class Suspected < Base
|
7
|
+
def initialize(id, member_pool, update_entry, send_ping_request)
|
8
|
+
super(id, member_pool, update_entry)
|
9
|
+
@ping_request_sent = !send_ping_request
|
10
|
+
@life_time_seconds = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def advance(elapsed_seconds)
|
14
|
+
@life_time_seconds += elapsed_seconds
|
15
|
+
unless @ping_request_sent
|
16
|
+
@member_pool.send_ping_request_to_k_members(@id)
|
17
|
+
@ping_request_sent = true
|
18
|
+
end
|
19
|
+
if @life_time_seconds > 60
|
20
|
+
Confirmed.new(@id, @member_pool, UpdateEntry.new(@id, :confirmed, @update_entry.incarnation_number, -2))
|
21
|
+
else
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def can_be_pinged?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|