rswim 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +1 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +79 -0
  9. data/Guardfile +42 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +72 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/run_node +30 -0
  15. data/bin/setup +8 -0
  16. data/lib/rswim.rb +33 -0
  17. data/lib/rswim/agent.rb +69 -0
  18. data/lib/rswim/directory.rb +27 -0
  19. data/lib/rswim/integration/deserializer.rb +51 -0
  20. data/lib/rswim/integration/serializer.rb +29 -0
  21. data/lib/rswim/integration/udp/node.rb +61 -0
  22. data/lib/rswim/logger.rb +31 -0
  23. data/lib/rswim/member/ack_responder.rb +22 -0
  24. data/lib/rswim/member/base.rb +11 -0
  25. data/lib/rswim/member/forwarding_state/base.rb +33 -0
  26. data/lib/rswim/member/forwarding_state/forwarding_ack.rb +26 -0
  27. data/lib/rswim/member/forwarding_state/ready.rb +24 -0
  28. data/lib/rswim/member/health_state/alive.rb +31 -0
  29. data/lib/rswim/member/health_state/base.rb +71 -0
  30. data/lib/rswim/member/health_state/confirmed.rb +31 -0
  31. data/lib/rswim/member/health_state/suspected.rb +32 -0
  32. data/lib/rswim/member/me.rb +45 -0
  33. data/lib/rswim/member/peer.rb +85 -0
  34. data/lib/rswim/member/transmission_state/awaiting_ack.rb +38 -0
  35. data/lib/rswim/member/transmission_state/base.rb +48 -0
  36. data/lib/rswim/member/transmission_state/off.rb +21 -0
  37. data/lib/rswim/member/transmission_state/ready.rb +27 -0
  38. data/lib/rswim/member/transmission_state/sending_ping.rb +30 -0
  39. data/lib/rswim/member/transmission_state/sending_ping_request.rb +31 -0
  40. data/lib/rswim/member_pool.rb +114 -0
  41. data/lib/rswim/message.rb +18 -0
  42. data/lib/rswim/node.rb +50 -0
  43. data/lib/rswim/pipe.rb +32 -0
  44. data/lib/rswim/protocol_state.rb +65 -0
  45. data/lib/rswim/status_report.rb +29 -0
  46. data/lib/rswim/update_entry.rb +17 -0
  47. data/lib/rswim/version.rb +5 -0
  48. data/log/.keep +0 -0
  49. data/rswim.gemspec +37 -0
  50. data/tmp/.keep +0 -0
  51. 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
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSwim
4
+ module Member
5
+ class Base
6
+ def initialize(id)
7
+ @id = id
8
+ end
9
+ end
10
+ end
11
+ 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