pokan 0.1.0rc1

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