pokan 0.1.0rc1
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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +37 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +60 -0
- data/lib/pokan.rb +19 -0
- data/lib/pokan/collective_peer_operations.rb +88 -0
- data/lib/pokan/connection.rb +27 -0
- data/lib/pokan/entity.rb +165 -0
- data/lib/pokan/event_handler.rb +113 -0
- data/lib/pokan/network.rb +43 -0
- data/lib/pokan/peer.rb +136 -0
- data/lib/pokan/query.rb +39 -0
- data/lib/pokan/request_handler.rb +90 -0
- data/lib/pokan/server.rb +230 -0
- data/lib/pokan/server_messages.rb +27 -0
- data/lib/pokan/version.rb +3 -0
- data/pokan.gemspec +53 -0
- data/spec/pokan/connection_spec.rb +10 -0
- data/spec/pokan/entity_spec.rb +147 -0
- data/spec/pokan/event_handler_spec.rb +84 -0
- data/spec/pokan/network_spec.rb +32 -0
- data/spec/pokan/peer_spec.rb +94 -0
- data/spec/pokan/query_spec.rb +67 -0
- data/spec/pokan/request_handler_spec.rb +253 -0
- data/spec/pokan/server_messages_spec.rb +81 -0
- data/spec/pokan/server_spec.rb +46 -0
- data/spec/spec_helper.rb +4 -0
- metadata +196 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# lib/pokan/event_handler.rb
|
2
|
+
|
3
|
+
module Pokan
|
4
|
+
|
5
|
+
class NoBlockGivenError < StandardError #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
# EventHandler is a class that associate blocks with events,
|
9
|
+
# making it possible to emit events and executing all the
|
10
|
+
# related callbacks (sequentially, in the order they were registered).
|
11
|
+
#
|
12
|
+
# === Usage
|
13
|
+
# e = EventHandler.new
|
14
|
+
# e.register(:my_event) { puts 'oh my!' }
|
15
|
+
# e.events #=> [:my_event]
|
16
|
+
# e.subscribers(:my_event) #=> Proc array with callbacks
|
17
|
+
# e.emit(:my_event) #=> 'oh my!' is printed
|
18
|
+
|
19
|
+
class EventHandler
|
20
|
+
|
21
|
+
# creates a new instance of EventHandler, ready for event registrations
|
22
|
+
def initialize
|
23
|
+
@callbacks = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# +register+ is the method used to associate a block with an event.
|
28
|
+
# The event name *must* be passed as a symbol, otherwise an exception is
|
29
|
+
# raised.
|
30
|
+
# You can call +register+ on the same event any number of times, but it is
|
31
|
+
# important to note that the blocks associated will be called in the same
|
32
|
+
# order they were registered.
|
33
|
+
#
|
34
|
+
# e = EventHandler.new
|
35
|
+
# e.register(:my_event) { puts 'my event' }.register(:my_event) { puts 'is cool!' }
|
36
|
+
def register(event, &block)
|
37
|
+
raise_unless_symbol(event)
|
38
|
+
|
39
|
+
raise NoBlockGivenError, 'must provide a block' unless block_given?
|
40
|
+
|
41
|
+
initialize_callbacks_for(event)
|
42
|
+
@callbacks[event] << block
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Retrieves the names of all the events for which there is at least
|
48
|
+
# one associated callback
|
49
|
+
def events
|
50
|
+
@callbacks.keys
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns an array with all the Proc objects for callbacks registered
|
55
|
+
# for the given _event_
|
56
|
+
def subscribers(event)
|
57
|
+
raise_unless_symbol(event)
|
58
|
+
|
59
|
+
@callbacks[event] || []
|
60
|
+
end
|
61
|
+
|
62
|
+
# Removes all the callbacks registered with _event_
|
63
|
+
def reset(event)
|
64
|
+
raise_unless_symbol(event)
|
65
|
+
|
66
|
+
@callbacks.delete(event)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Emits the given _event_, thus calling all the registered callbacks
|
72
|
+
# up to that point. If there are no registered callbacks for the
|
73
|
+
# _event_ passed, nothing is done.
|
74
|
+
#
|
75
|
+
# If the registered callbacks expected parameters, pass them using the
|
76
|
+
# +with+ option
|
77
|
+
#
|
78
|
+
# e = EventHandler.new
|
79
|
+
# e.register(:awesome) { puts 'awesome!' }
|
80
|
+
# e.register(:awesome) { puts 'not that awesome...' }
|
81
|
+
# e.emit(:awesome) #=> messages are print
|
82
|
+
#
|
83
|
+
# Using the +with+ option:
|
84
|
+
#
|
85
|
+
# e = EventHandler.new
|
86
|
+
# e.register(:awesome) { |msg| puts msg }
|
87
|
+
# e.emit(:awesome, :with => ['awesome!']) #=> 'awesome!' is print
|
88
|
+
def emit(event, options = {})
|
89
|
+
raise_unless_symbol(event)
|
90
|
+
|
91
|
+
args = options[:with] || []
|
92
|
+
|
93
|
+
if @callbacks.has_key? event
|
94
|
+
@callbacks[event].each { |c| c.call(*args) }
|
95
|
+
end
|
96
|
+
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Raises an +ArgumentError+ if the given parameter is not a symbol
|
103
|
+
def raise_unless_symbol(event)
|
104
|
+
raise ArgumentError, 'event name must be passed as a symbol' unless event.is_a? Symbol
|
105
|
+
end
|
106
|
+
|
107
|
+
# If there hasn't been any callback registered to _event_, create a new list for it
|
108
|
+
def initialize_callbacks_for(event)
|
109
|
+
@callbacks[event] ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# lib/pokan/network.rb
|
2
|
+
|
3
|
+
module Pokan
|
4
|
+
|
5
|
+
# Network is a helper class, used by the Pokan::Server class.
|
6
|
+
# Its main goal is to provide a higher abstraction for networking
|
7
|
+
# methods provided by the Ruby core. It should not be used by
|
8
|
+
# user code.
|
9
|
+
class Network
|
10
|
+
|
11
|
+
##
|
12
|
+
# Sends _message_ to the given _host_ and _port_. As UDP is connectionless,
|
13
|
+
# there's no way to be sure wheter or not the message will reach its destination.
|
14
|
+
# UDP messages are used for communication of peers.
|
15
|
+
def self.udp(message, host, port)
|
16
|
+
@socket = UDPSocket.new
|
17
|
+
bytes = @socket.send(message, 0, host, port)
|
18
|
+
|
19
|
+
bytes > 0
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Sends _message_ to the given _host_ and _port_. TCP is a connection-oriented
|
24
|
+
# protocol, so we need to have a server waiting for the message in the destination
|
25
|
+
# passed as parameters.
|
26
|
+
# If the connection cannot be established, this method returns +false+.
|
27
|
+
# TCP messages are used for communication with seeds, since we need to be
|
28
|
+
# sure the data really reaches its destination (for example, when someone finishes
|
29
|
+
# the gossip server manually, pokan sends a message to a seed, telling it is about
|
30
|
+
# to die.
|
31
|
+
def self.tcp(message, host, port)
|
32
|
+
begin
|
33
|
+
@socket = TCPSocket.open(host, port)
|
34
|
+
bytes = @socket.send(message, 0)
|
35
|
+
|
36
|
+
bytes > 0
|
37
|
+
rescue
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/pokan/peer.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# lib/pokan/peer.rb
|
2
|
+
module Pokan
|
3
|
+
|
4
|
+
##
|
5
|
+
# Peer is a Entity that takes care of the peer semantics.
|
6
|
+
# It controls the status, id and role attributes.
|
7
|
+
class Peer < Entity
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
store(:role, 'peer', 0)
|
12
|
+
store(:status, 'alive', 0)
|
13
|
+
end
|
14
|
+
|
15
|
+
def address=(address)
|
16
|
+
@address = address
|
17
|
+
self.id = "#{@address}:#{@udp_port}" if defined?(@address) && defined?(@udp_port)
|
18
|
+
end
|
19
|
+
|
20
|
+
def address
|
21
|
+
@address = id.split(':').first
|
22
|
+
end
|
23
|
+
|
24
|
+
def udp_port=(port)
|
25
|
+
@udp_port = port
|
26
|
+
self.id = "#{@address}:#{@udp_port}" if defined?(@address) && defined?(@udp_port)
|
27
|
+
end
|
28
|
+
|
29
|
+
def udp_port
|
30
|
+
@udp_port = id.split(':')[1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def tcp_port=(port)
|
34
|
+
store(:tcp_port, port)
|
35
|
+
end
|
36
|
+
|
37
|
+
def tcp_port
|
38
|
+
value(:tcp_port)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Stores the value and the timestamp for a given key.
|
43
|
+
# If the key, which must be a symbol, already exists, it will be updated if
|
44
|
+
# the timestamp is greater.
|
45
|
+
# The timestamp defaults to Time.now.
|
46
|
+
#
|
47
|
+
# If the key is ':role', the values have to be 'peer' or 'seed'. In the same
|
48
|
+
# way, if the key is ':status', the value have to be 'alive' or 'dead'.
|
49
|
+
# Otherwise, the value will not be set.
|
50
|
+
def store(key, value, timestamp = Time.now)
|
51
|
+
case key
|
52
|
+
when :role
|
53
|
+
super(:role, value, timestamp) if ['peer', 'seed'].include?(value)
|
54
|
+
when :status
|
55
|
+
super(:status, value, timestamp) if ['alive', 'dead'].include?(value)
|
56
|
+
else
|
57
|
+
super(key, value, timestamp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Changes the role of the peer. The role must be peer or seed. Otherwise, the role will stay as it was and no exception will be thrown.
|
63
|
+
def role=(role)
|
64
|
+
store(:role, role)
|
65
|
+
end
|
66
|
+
|
67
|
+
def role
|
68
|
+
value(:role)
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Turns the peer into a seed.
|
73
|
+
def act_as_seed
|
74
|
+
store(:role, 'seed')
|
75
|
+
end
|
76
|
+
|
77
|
+
def seed?
|
78
|
+
value(:role) == 'seed'
|
79
|
+
end
|
80
|
+
|
81
|
+
def peer?
|
82
|
+
value(:role) == 'peer'
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Changes the status of the peer.
|
87
|
+
# The status must be alive or dead. Otherwise, the role will stay as it was and no exception will be thrown.
|
88
|
+
def status=(status)
|
89
|
+
store(:status, status)
|
90
|
+
end
|
91
|
+
|
92
|
+
def status
|
93
|
+
value(:status)
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Revives the peer
|
98
|
+
def revive
|
99
|
+
store(:status, 'alive')
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Kills the peer
|
104
|
+
def kill
|
105
|
+
store(:status, 'dead')
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def alive?
|
110
|
+
status == 'alive'
|
111
|
+
end
|
112
|
+
|
113
|
+
def dead?
|
114
|
+
status == 'dead'
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Gossips with the given seed and replicates its local storage.
|
119
|
+
#
|
120
|
+
# If the host or port is invalid, the local storage will continue empty.
|
121
|
+
# === Usage
|
122
|
+
# peer = Pokan::Peer.new
|
123
|
+
# peer.address = '127.0.0.2'
|
124
|
+
# peer.port = '1234'
|
125
|
+
# peer.value(:key_not_present) # => nil
|
126
|
+
# peer.gossip_with('127.0.0.2', '8888')
|
127
|
+
# peer.value(:key_not_present) # => 'the value of the key'
|
128
|
+
def sync_with(host, port)
|
129
|
+
db = Connection.redis
|
130
|
+
db.slaveof(host, port)
|
131
|
+
sleep(3) #while db.info['master_sync_in_progress'].to_i == 0
|
132
|
+
@seed = host
|
133
|
+
db.slaveof('no', 'one')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/pokan/query.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# lib/pokan/query.rb
|
2
|
+
|
3
|
+
module Pokan
|
4
|
+
##
|
5
|
+
# The Query class is used to find Entities that obey restrictions.
|
6
|
+
# It is commonly used to get a Entity according with a id, get all dead Peers
|
7
|
+
# and so on.
|
8
|
+
class Query
|
9
|
+
def initialize(entity_class)
|
10
|
+
@entity_class = entity_class
|
11
|
+
@db = Connection.redis
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Returns a array of entities respecting the given restrictions
|
16
|
+
# Structure: {id: '234', role: 'dead', status:['alive', 'dead'], random: true...}
|
17
|
+
def where(query)
|
18
|
+
result = []
|
19
|
+
candidates = @db.smembers('entities')
|
20
|
+
|
21
|
+
if query.has_key?(:id)
|
22
|
+
query[:id] = [query[:id]] unless query[:id].is_a?(Array)
|
23
|
+
candidates = candidates & query[:id]
|
24
|
+
end
|
25
|
+
candidates = candidates - query[:not] if query.has_key?(:not)
|
26
|
+
|
27
|
+
candidates.each do |entity_id|
|
28
|
+
entity = @entity_class.new
|
29
|
+
entity.id = entity_id
|
30
|
+
entity.reload
|
31
|
+
result << entity if entity.match?(query.reject {|k, v| k == :not || k == :random || k == :id})
|
32
|
+
end
|
33
|
+
|
34
|
+
result = [result.sample] if query[:random]
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# lib/pokan/gossiper.rb
|
2
|
+
require 'json'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Pokan
|
6
|
+
|
7
|
+
# Gossiper module is the implementation of the push-pull-gossip protocol.
|
8
|
+
class RequestHandler < EM::Connection
|
9
|
+
include Pokan::CollectivePeerOperations
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :address, :port
|
13
|
+
|
14
|
+
alias_method :old_new, :new
|
15
|
+
|
16
|
+
def new(sig, *args)
|
17
|
+
rh = old_new(sig, *args)
|
18
|
+
rh.address = address
|
19
|
+
rh.port = port
|
20
|
+
|
21
|
+
rh
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :address, :port
|
26
|
+
|
27
|
+
##
|
28
|
+
# Receives the gossip message and returns the apropriate message
|
29
|
+
# - digest message -> pull message
|
30
|
+
# - pull message -> push message and newer keys stored
|
31
|
+
# - push message -> newer keys stored
|
32
|
+
# - hello message -> peer created locally
|
33
|
+
# - goodbye message -> peer killed locally
|
34
|
+
def receive_data(json_data)
|
35
|
+
message = JSON.parse(json_data)
|
36
|
+
data = message['data']
|
37
|
+
|
38
|
+
puts 'RECV'
|
39
|
+
case message['action']
|
40
|
+
when 'digest'
|
41
|
+
puts "DIGEST from #{message['origin']}"
|
42
|
+
pull = pull_message(data)
|
43
|
+
send_datagram(pull, *message['origin'].split(':'))
|
44
|
+
|
45
|
+
pull
|
46
|
+
when 'pull'
|
47
|
+
puts "PULL from #{message['origin']}"
|
48
|
+
response = push_message(data['older'])
|
49
|
+
merge(data['newer'])
|
50
|
+
send_datagram(response, *message['origin'].split(':'))
|
51
|
+
|
52
|
+
response
|
53
|
+
when 'push'
|
54
|
+
puts "PUSH from #{message['origin']}"
|
55
|
+
merge(data)
|
56
|
+
when 'hello'
|
57
|
+
puts "HELLO from from #{message['origin']}"
|
58
|
+
peer = Peer.new
|
59
|
+
peer.id = message['origin']
|
60
|
+
# peer.store(:role, message['role'])
|
61
|
+
peer.save
|
62
|
+
when 'goodbye'
|
63
|
+
puts "GOODBYE from #{message['origin']}"
|
64
|
+
peer = Query.new(Peer).where(id: message['origin'])[0]
|
65
|
+
peer.store(:status, 'dead', message['timestamp'])
|
66
|
+
peer.save
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Get a pull message containing all status and keys based on the given keys following the structure:
|
72
|
+
# Given structure: {id: [:key1, :key2, ...]}
|
73
|
+
def push_message(keys)
|
74
|
+
{ action: 'push', data: retrieve(keys), origin:"#{address}:#{port}" }.to_json
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Get a push message containing all status and keys based on the given keys following the structure:
|
79
|
+
# Given structure: {id: {key: timestamp, ...} ...}
|
80
|
+
def pull_message(keys)
|
81
|
+
{ action: 'pull',
|
82
|
+
data: {
|
83
|
+
newer: newer(keys),
|
84
|
+
older: older(keys)
|
85
|
+
},
|
86
|
+
origin:"#{address}:#{port}"
|
87
|
+
}.to_json
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/pokan/server.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# lib/pokan/server.rb
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Pokan
|
6
|
+
|
7
|
+
##
|
8
|
+
# Gossip server class, using the Pokan::Peer class. This is the main class
|
9
|
+
# instantied by user code.
|
10
|
+
#
|
11
|
+
# When a server class is created, the user should set:
|
12
|
+
# * the gossip interval
|
13
|
+
# * actual data to be stored, and possibly shared with other servers
|
14
|
+
# accross the network
|
15
|
+
# * The server behavior on a defined set of events, declared in an
|
16
|
+
# event driven syntax.
|
17
|
+
#
|
18
|
+
# The user can also use epoll[http://linux.die.net/man/4/epoll]
|
19
|
+
# for faster I/O notification, if the server is running on a Linux system.
|
20
|
+
#
|
21
|
+
# Sample usage is shown below:
|
22
|
+
#
|
23
|
+
# s = Pokan::Server.new
|
24
|
+
# s.gossip_every(1) #=> tells the server to gossip every second
|
25
|
+
# s.use_epoll #=> I'm in a Linux system
|
26
|
+
#
|
27
|
+
# s.store 'k', 'v'
|
28
|
+
#
|
29
|
+
# e.on :new_key do |key, value|
|
30
|
+
# puts "What a great key I just got: #{key}!"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# # more event related code...
|
34
|
+
#
|
35
|
+
# e.start #=> last command, blocks execution
|
36
|
+
class Server < Peer
|
37
|
+
|
38
|
+
attr_reader :gossip_interval
|
39
|
+
alias_method :port, :udp_port
|
40
|
+
|
41
|
+
include ServerMessages
|
42
|
+
|
43
|
+
ON_EVENTS = [:new_key, :key_changed, :key_removed]
|
44
|
+
BEFORE_EVENTS = [:gossip, :sync]
|
45
|
+
AFTER_EVENTS = [:gossip, :sync]
|
46
|
+
|
47
|
+
# Creates a new gossip server. You must pass the machine's IP address in
|
48
|
+
# the network. This information will be used when changing information
|
49
|
+
# with other peers running Pokan.
|
50
|
+
# This also set some defaults, namely:
|
51
|
+
# * server will bind TCP and UDP port number 5555. If you have something else
|
52
|
+
# running, you must set them like:
|
53
|
+
# server.tcp_port = 9876
|
54
|
+
# server.udp_port = 9989
|
55
|
+
#
|
56
|
+
# * +epoll+ will not be used (you might want to set it if you are in a
|
57
|
+
# Linux system. See Pokan::Server#use_epoll
|
58
|
+
#
|
59
|
+
# * The server will comunicate with other peers _every second_
|
60
|
+
#
|
61
|
+
# Of course, all of these are customizable to your particular needs.
|
62
|
+
def initialize(address)
|
63
|
+
@event_handler = EventHandler.new
|
64
|
+
self.address = address
|
65
|
+
self.udp_port = 5555
|
66
|
+
super()
|
67
|
+
initialize_ivs
|
68
|
+
end
|
69
|
+
|
70
|
+
# Call this method if you want to use epoll in your Linux system.
|
71
|
+
# *Warning*: this will not throw an exception nor will warn you
|
72
|
+
# if you use this method in a non-Linux machine. You might face
|
73
|
+
# errors when you try to start the server, though.
|
74
|
+
def use_epoll
|
75
|
+
@epoll = true
|
76
|
+
end
|
77
|
+
|
78
|
+
def using_epoll?
|
79
|
+
@epoll
|
80
|
+
end
|
81
|
+
|
82
|
+
# Use this method to specify the time interval you wish to use for
|
83
|
+
# changing gossip messages among peers. Default is 1 second.
|
84
|
+
def gossip_every(seconds)
|
85
|
+
@gossip_interval = seconds
|
86
|
+
end
|
87
|
+
|
88
|
+
# Stores a key, value pair that will be shared among all the peers in
|
89
|
+
# the network.
|
90
|
+
def store(key, value, timestamp=Time.now)
|
91
|
+
super
|
92
|
+
save
|
93
|
+
@event_handler.emit :new_key, :with => [key, value]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Defines the behaviour you want the server to have on different events.
|
97
|
+
# For now, you can define your behaviour for the following events:
|
98
|
+
# * +:new_key+ - This will be called whenever a new_key is inserted
|
99
|
+
# (and there was no previous value for that)
|
100
|
+
#
|
101
|
+
# * +:key_changed+ - This is called when a value for a certain key is
|
102
|
+
# updated (a value with greater timestamp)
|
103
|
+
#
|
104
|
+
# * +:key_removed+ - The name speaks for itself
|
105
|
+
#
|
106
|
+
# For all the events, both the key and the value are passed to the
|
107
|
+
# block.
|
108
|
+
#
|
109
|
+
# Example:
|
110
|
+
#
|
111
|
+
# server = Pokan::Server.new('10.10.10.8')
|
112
|
+
# server.on :new_key do |key, value|
|
113
|
+
# puts "New key #{key} was inserted with value #{value}!"
|
114
|
+
# end
|
115
|
+
def on(event, &block)
|
116
|
+
register_event(event, &block) if ON_EVENTS.include? event
|
117
|
+
end
|
118
|
+
|
119
|
+
# Adds behaviour before a given event occurs. For now, the only event
|
120
|
+
# supported is +:gossip+. Use this method if you want some code to run
|
121
|
+
# every time before the server gossips with a random peer.
|
122
|
+
def before(event, &block)
|
123
|
+
event_name = prepend('before_', event)
|
124
|
+
register_event(event_name, &block) if BEFORE_EVENTS.include? event
|
125
|
+
end
|
126
|
+
|
127
|
+
# Same as the Pokan::Server#before, but it also supports the +:sync+ event.
|
128
|
+
# Use this method to run some code after the server gossips with a random
|
129
|
+
# peer or after it syncs with a seed (if you passed one on initialization.
|
130
|
+
# See Pokan::Server#start).
|
131
|
+
def after(event, &block)
|
132
|
+
event_name = prepend('after_', event)
|
133
|
+
register_event(event_name, &block) if AFTER_EVENTS.include? event
|
134
|
+
end
|
135
|
+
|
136
|
+
# This method starts the server, which will bind specified TCP and UDP
|
137
|
+
# ports (or 5555 by default). Optionally, you can pass the address of
|
138
|
+
# another pokan instance (a seed), and the server will automatically
|
139
|
+
# send a message to the seed and update its data. If no seed information
|
140
|
+
# is passed, the server will start with no data and assume it is
|
141
|
+
# a seed.
|
142
|
+
#
|
143
|
+
# Example:
|
144
|
+
#
|
145
|
+
# server = Pokan::Server.new('10.10.10.8')
|
146
|
+
# server.start :gossip_with => '10.10.10.9:5555'
|
147
|
+
#
|
148
|
+
# In the example above, the server will try to contact (via TCP) a pokan
|
149
|
+
# server running on +10.10.10.9+, port +5555+, and update its data
|
150
|
+
# accordingly.
|
151
|
+
def start(options = {})
|
152
|
+
seed = options[:gossip_with]
|
153
|
+
|
154
|
+
if seed.nil?
|
155
|
+
act_as_seed
|
156
|
+
else
|
157
|
+
@seed_address, @seed_port = parse_host_and_port(seed)
|
158
|
+
say_hello(@seed_address, @seed_port)
|
159
|
+
|
160
|
+
sync_with(@seed_address, options[:redis])
|
161
|
+
@event_handler.emit :after_sync
|
162
|
+
end
|
163
|
+
|
164
|
+
start_event_loop
|
165
|
+
end
|
166
|
+
|
167
|
+
# Finishes the server
|
168
|
+
def stop
|
169
|
+
kill
|
170
|
+
EventMachine.stop
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
# Sets default variables, such as:
|
176
|
+
# * TCP and UDP ports: 5555
|
177
|
+
# * do _not_ use epoll
|
178
|
+
# * gossip every second
|
179
|
+
def initialize_ivs
|
180
|
+
self.tcp_port = 5555
|
181
|
+
@epoll = false
|
182
|
+
@gossip_interval = 1
|
183
|
+
end
|
184
|
+
|
185
|
+
def register_event(event, &block)
|
186
|
+
@event_handler.register(event, &block)
|
187
|
+
end
|
188
|
+
|
189
|
+
def parse_host_and_port(address)
|
190
|
+
address.split ':'
|
191
|
+
end
|
192
|
+
|
193
|
+
def prepend(string, event)
|
194
|
+
event_str = string + event.to_s
|
195
|
+
event_str.to_sym
|
196
|
+
end
|
197
|
+
|
198
|
+
def start_event_loop
|
199
|
+
EventMachine.run {
|
200
|
+
gossip
|
201
|
+
|
202
|
+
RequestHandler.address = address
|
203
|
+
RequestHandler.port = udp_port
|
204
|
+
|
205
|
+
EventMachine.open_datagram_socket address, udp_port, RequestHandler
|
206
|
+
EventMachine.start_server address, tcp_port, RequestHandler
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
def say_hello(address, port)
|
211
|
+
Network.tcp(hello_message, address, port)
|
212
|
+
end
|
213
|
+
|
214
|
+
def gossip
|
215
|
+
EventMachine.add_periodic_timer(@gossip_interval) {
|
216
|
+
query = Query.new(Peer)
|
217
|
+
peer = query.where(:random => true, :role => ['seed', 'peer'], :not => [id],
|
218
|
+
:status => 'alive').first
|
219
|
+
|
220
|
+
unless peer.nil?
|
221
|
+
@event_handler.emit :before_gossip
|
222
|
+
Network.udp(digest_message, peer.address, peer.udp_port)
|
223
|
+
@event_handler.emit :after_gossip, :with => [peer.address, peer.udp_port]
|
224
|
+
end
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|