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.
@@ -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
@@ -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
@@ -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