pokan 0.1.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|