pokan-cluster 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec"
5
+ gem "guard-rspec"
6
+ gem "bundler"
7
+ gem "jeweler"
8
+ gem "pry"
9
+ end
10
+
11
+ gem "pokan", "~> 0.1.0rc2"
12
+ gem "host"
13
+ gem "yagni", "~> 1.2"
14
+ gem "logging"
15
+
16
+ #TODO: remove this group
17
+ group :local do
18
+ gem "redis"
19
+ gem "eventmachine"
20
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,65 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ coderay (0.9.8)
5
+ diff-lcs (1.1.3)
6
+ eventmachine (0.12.10)
7
+ git (1.2.5)
8
+ guard (0.8.4)
9
+ thor (~> 0.14.6)
10
+ guard-rspec (0.4.5)
11
+ guard (>= 0.4.0)
12
+ host (0.1)
13
+ jeweler (1.6.4)
14
+ bundler (~> 1.0)
15
+ git (>= 1.2.5)
16
+ rake
17
+ json (1.6.1)
18
+ little-plugger (1.1.2)
19
+ logging (1.6.1)
20
+ little-plugger (>= 1.1.2)
21
+ method_source (0.6.7)
22
+ ruby_parser (>= 2.3.1)
23
+ pokan (0.1.0rc2)
24
+ eventmachine
25
+ eventmachine
26
+ json
27
+ redis
28
+ redis
29
+ pry (0.9.7.3)
30
+ coderay (~> 0.9.8)
31
+ method_source (~> 0.6.7)
32
+ ruby_parser (>= 2.3.1)
33
+ slop (~> 2.1.0)
34
+ rake (0.9.2)
35
+ redis (2.2.2)
36
+ rspec (2.6.0)
37
+ rspec-core (~> 2.6.0)
38
+ rspec-expectations (~> 2.6.0)
39
+ rspec-mocks (~> 2.6.0)
40
+ rspec-core (2.6.4)
41
+ rspec-expectations (2.6.0)
42
+ diff-lcs (~> 1.1.2)
43
+ rspec-mocks (2.6.0)
44
+ ruby_parser (2.3.1)
45
+ sexp_processor (~> 3.0)
46
+ sexp_processor (3.0.7)
47
+ slop (2.1.0)
48
+ thor (0.14.6)
49
+ yagni (1.2)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ bundler
56
+ eventmachine
57
+ guard-rspec
58
+ host
59
+ jeweler
60
+ logging
61
+ pokan (~> 0.1.0rc2)
62
+ pry
63
+ redis
64
+ rspec
65
+ yagni (~> 1.2)
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'rspec', :cli => '--color --format doc' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/pokan-cluster.rb}) { |m| "spec/pokan-cluster_spec.rb" }
4
+ watch(%r{^lib/pokan-cluster/(.+)\.rb$}) { |m| "spec/pokan-cluster/#{m[1]}_spec.rb" }
5
+ watch("spec/spec_helper.rb") { "spec" }
6
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Renato Mascarenhas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = pokan-cluster
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to pokan-cluster
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 rmascarenhas. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('lib/pokan-cluster/version')
4
+
5
+ require 'rubygems'
6
+ require 'bundler'
7
+ begin
8
+ Bundler.setup(:default, :development)
9
+ rescue Bundler::BundlerError => e
10
+ $stderr.puts e.message
11
+ $stderr.puts "Run `bundle install` to install missing gems"
12
+ exit e.status_code
13
+ end
14
+ require 'rake'
15
+
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gem|
18
+ gem.name = "pokan-cluster"
19
+ gem.version = Pokan::Cluster::VERSION
20
+
21
+ gem.homepage = "http://github.com/rmascarenhas/pokan-cluster"
22
+ gem.license = "MIT"
23
+
24
+ gem.summary = %Q{pokan-cluster is a set of neat, handy functionalities built
25
+ on top of pokan.}
26
+
27
+ gem.description = <<-END
28
+ pokan-cluster adds a handy set of functionalities on top of pokan, everyone's
29
+ favorite Ruby Gossip protocol implementation. Logging, easy YAML configuration
30
+ file, workload information of peers in your network (so that you can write a
31
+ simple load balancer) are among the features pokan-cluster has to offer!
32
+ Take a look at it and contribute! (see our Github page).
33
+ END
34
+ gem.email = "renato.mascosta@gmail.com"
35
+ gem.authors = ["Fabio Lima Pereira", "Rafael Regis do Prado", "Renato Mascarenhas"]
36
+
37
+ gem.add_dependency 'pokan'
38
+ gem.add_dependency 'yagni'
39
+ gem.add_dependency 'host'
40
+
41
+ end
42
+ Jeweler::RubygemsDotOrgTasks.new
43
+
44
+ require 'rspec/core'
45
+ require 'rspec/core/rake_task'
46
+ RSpec::Core::RakeTask.new(:spec) do |spec|
47
+ spec.pattern = FileList['spec/**/*_spec.rb']
48
+ end
49
+
50
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
51
+ spec.pattern = 'spec/**/*_spec.rb'
52
+ spec.rcov = true
53
+ end
54
+
55
+ task :default => :spec
56
+
57
+ require 'rake/rdoctask'
58
+ Rake::RDocTask.new do |rdoc|
59
+ version = Pokan::Cluster::VERSION
60
+
61
+ rdoc.rdoc_dir = 'rdoc'
62
+ rdoc.title = "pokan-cluster #{version}"
63
+ rdoc.rdoc_files.include('README*')
64
+ rdoc.rdoc_files.include('lib/**/*.rb')
65
+ end
@@ -0,0 +1,247 @@
1
+ require 'pokan'
2
+ require 'yell/hash'
3
+ require 'logging'
4
+
5
+ module Pokan
6
+
7
+ require_relative 'pokan-cluster/configuration'
8
+ require_relative 'pokan-cluster/prepared_string'
9
+ require_relative 'pokan-cluster/autorunner'
10
+ require_relative 'pokan-cluster/log_messages'
11
+ require_relative 'pokan-cluster/config_defaults'
12
+ require_relative 'pokan-cluster/version'
13
+
14
+ # Cluster module, to be included on all classes that want to implement
15
+ # a custom Gossip server. Including this module enables the developer to
16
+ # customize the configuration file location (and format, as long as she
17
+ # provided a parser for that format). The user can also pass manually
18
+ # the values for the configuration options. However, this is not the
19
+ # recommended way, of course.
20
+ #
21
+ # Example
22
+ #
23
+ # class MyCustomGossiper
24
+ # include Pokan::Cluster
25
+ #
26
+ # config { JSON.parse(CONFIG_PATH) }
27
+ #
28
+ # end
29
+ #
30
+ # The `config` method expects a hash containing values for network connection,
31
+ # seed address, log information, and others. If you want to see an example of a
32
+ # valid configuration hash, you can parse the following YAML (recommened config
33
+ # format for pokan-cluster):
34
+ #
35
+ # pokan:
36
+ # address: "10.10.10.8" # your IP address
37
+ #
38
+ # connect:
39
+ # tcp: 4444
40
+ # udp: 8787 # ports that will be binded
41
+ # epoll: true # Linux machine?
42
+ #
43
+ # gossip:
44
+ # every: 2 # seconds
45
+ # share: "cpu,memory,load" # these are the valid options (separated by comma).
46
+ #
47
+ # seed: "10.10.10.2" # will be contacted on startup
48
+ #
49
+ # log:
50
+ # enabled: true # always good!
51
+ # level: "info"
52
+ # file: "/path/to/my/log" # default is STDOUT
53
+ #
54
+ # Optionally, you can directly pass a file, using the `config_file` method. However,
55
+ # only the YAML format is currently supported
56
+ #
57
+ # class MyCustomGossiper
58
+ # include Pokan::Cluster
59
+ #
60
+ # config_file = '/path/to/my/config.yml'
61
+ # end
62
+ module Cluster
63
+
64
+
65
+ # When a server includes the Pokan::Cluster module, it gains access to its
66
+ # class methods, allowing the user to set configuration and behavior of the
67
+ # server. It is also configured to automatically run when the server class
68
+ # is loaded.
69
+ def self.included(klass)
70
+ klass.extend ClassMethods
71
+ Autorunner.register_server(klass)
72
+ end
73
+
74
+ # Automatically starts registered servers.
75
+ at_exit {
76
+ unless $! || Pokan::Cluster::Autorunner.run?
77
+ Pokan::Cluster::Autorunner.run
78
+ end
79
+ }
80
+
81
+ module ClassMethods
82
+
83
+ attr_accessor :address, :tcp_port, :udp_port, :epoll, :gossip_interval,
84
+ :seed, :logging, :log_level, :log_file
85
+
86
+ # Pretty names
87
+ alias_method :epoll?, :epoll
88
+ alias_method :logging?, :logging
89
+
90
+ # Loads the server configuration, expecting a valid config hash in a block.
91
+ # This allows you to use whatever configuration format, as long as you can
92
+ # transform it to a valid pokan hash.
93
+ def config(&block)
94
+ @server || load!(block.call)
95
+ initialize_log
96
+ delegate
97
+ end
98
+
99
+ # Convenience method, allowing the user to set the file containing the
100
+ # configuration in the YAML format. In the future, maybe add builtin support
101
+ # for other different formats.
102
+ def config_file(path, options={})
103
+ config { YAML.load(File.read path) }
104
+ end
105
+
106
+ # Disables log for the gossip server
107
+ def disable_log
108
+ @logging = false
109
+ end
110
+
111
+ # Sets the seed address and redis port, if specified
112
+ #
113
+ # class Server
114
+ # include Pokan::Cluster
115
+ #
116
+ # gossip_with '10.10.10.3', :redis => 6549
117
+ # end
118
+ #
119
+ # If no Redis port is specified, +6379+ is assumed (as it is the default
120
+ # Redis port).
121
+ def gossip_with(address, options)
122
+ update_autorunner_option(:gossip_with, address)
123
+ update_autorunner_option(:redis, options[:redis] || DEFAULTS[:redis])
124
+ end
125
+
126
+ def method_missing(meth, *args, &block)
127
+ @delegator.send(meth, *args, &block)
128
+ end
129
+
130
+ private
131
+
132
+ # Sets variables according to the hash passed.
133
+ def load!(config_hash)
134
+ config = Configuration.new(config_hash)
135
+ @address = config.get('pokan.address')
136
+ @server = Pokan::Server.new(@address)
137
+
138
+ update_ivs(config)
139
+ config_autorunner
140
+ end
141
+
142
+ def update_ivs(config)
143
+ @tcp_port = config.get('pokan.connect.tcp') || DEFAULTS[:tcp_port]
144
+ @udp_port = config.get('pokan.connect.udp') || DEFAULTS[:udp_port]
145
+ @epoll = config.get('pokan.connect.epoll') || DEFAULTS[:epoll]
146
+ @gossip_interval = config.get('pokan.gossip.every') || DEFAULTS[:gossip_interval]
147
+ @seed = config.get('pokan.seed.address')
148
+ @redis = config.get('pokan.seed.redis') || DEFAULTS[:redis]
149
+ @logging = config.get('pokan.log.enabled') || DEFAULTS[:logging]
150
+ @log_level = config.get('pokan.log.level').to_sym || DEFAULTS[:level]
151
+ @log_file = config.get('pokan.log.file')
152
+ end
153
+
154
+ def config_autorunner
155
+ options = {}
156
+ options.merge!({ :gossip_with => @seed }) unless @seed.nil?
157
+ options.merge!({ :redis => @redis }) unless @redis.nil?
158
+
159
+ Autorunner.options = options
160
+ end
161
+
162
+ def update_autorunner_option(key, value)
163
+ opt = Autorunner.options
164
+ opt[key] = value
165
+
166
+ Autorunner.options = opt
167
+ end
168
+
169
+ # Adds callbacks for logging on the happening of certain events, such
170
+ # as new keys being defined, changed, gossip messages, etc.
171
+ def initialize_log
172
+ @log_initialized ||= false
173
+
174
+ if logging? && !@log_initialized
175
+ setup_logging
176
+ add_log_callbacks
177
+ end
178
+
179
+ @log_initialized = true
180
+ end
181
+
182
+ # requires and sets up the `logging` library.
183
+ def setup_logging
184
+ @logger = Logging.logger['pokan-cluster']
185
+ appenders = [Logging.appenders.stdout]
186
+ appenders << Logging.appenders.file(@log_file) if @log_file
187
+
188
+ @logger.add_appenders *appenders
189
+ @logger.level = @log_level
190
+ end
191
+
192
+ # adds callbacks on the server instance so that some events are logged
193
+ def add_log_callbacks
194
+ @server.on(:start) do |address, tcp_port, udp_port, epoll, gossip_interval, seed_address, seed_port|
195
+ @logger.debug LOG_MESSAGES[:start][:debug].with(address, tcp_port, udp_port, epoll,
196
+ gossip_interval, seed_address, @log_level.to_s, @log_file).string
197
+
198
+ @logger.info LOG_MESSAGES[:start][:info].with(address).string
199
+ end
200
+
201
+ @server.before(:sync) do |address, port|
202
+ @logger.debug LOG_MESSAGES[:before_sync][:debug].with(address, port).string
203
+ @logger.info LOG_MESSAGES[:before_sync][:info].string
204
+ end
205
+
206
+ @server.after(:sync) do |time_elapsed|
207
+ @logger.debug LOG_MESSAGES[:after_sync][:debug].with(time_elapsed).string
208
+ @logger.info LOG_MESSAGES[:after_sync][:info].string
209
+ end
210
+
211
+ @server.on(:new_key) do |key, value|
212
+ @logger.debug LOG_MESSAGES[:new_key][:debug].with(key.to_s, value).string
213
+ end
214
+
215
+ @server.before(:gossip) do |address, port, digest_length|
216
+ @logger.debug LOG_MESSAGES[:before_gossip][:debug].with(address, digest_length).string
217
+ @logger.info LOG_MESSAGES[:before_gossip][:info].with(address).string
218
+ end
219
+
220
+ @server.after(:gossip) do
221
+ @logger.debug LOG_MESSAGES[:after_gossip][:debug].string
222
+ end
223
+
224
+ @server.on(:shutdown) do
225
+ @logger.info LOG_MESSAGES[:shutdown]
226
+ end
227
+
228
+
229
+ end
230
+
231
+ # Initializes the delegator instance so that unknown methods will
232
+ # be delegated to the server instance, providing a better API for
233
+ # developers who want to customize their server behavior.
234
+ def delegate
235
+ require 'delegate'
236
+ @delegator = DelegateClass(Pokan::Server).new(@server)
237
+ end
238
+
239
+ def prepend(a_string, to_string)
240
+ (a_string.to_s + to_string.to_s).to_sym
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+
247
+ end
@@ -0,0 +1,34 @@
1
+ module Pokan
2
+
3
+ module Cluster
4
+
5
+ # Class used for automatically run a server that include Pokan::Cluster
6
+ # module.
7
+ class Autorunner
8
+
9
+ class << self
10
+ attr_accessor :server
11
+ attr_accessor :options
12
+ end
13
+
14
+ # Registers a new server, that will be started when the method
15
+ # Pokan::Cluster::Autorunner.run gets called.
16
+ def self.register_server(server_class)
17
+ @server = server_class
18
+ end
19
+
20
+ # Checks if we have any server to start
21
+ def self.run?
22
+ @server.respond_to? :start
23
+ end
24
+
25
+ # Calls the method `start` on every registered server
26
+ def self.run
27
+ @server.start(@options)
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,21 @@
1
+ module Pokan
2
+ module Cluster
3
+
4
+ # Default values for configuration, specifying:
5
+ # * TCP and UDP ports
6
+ # * epoll set to false
7
+ # * logging enabled to `STDOUT`
8
+ # * gossip interval: 1 second
9
+
10
+ DEFAULTS = {
11
+ :tcp_port => 5555,
12
+ :udp_port => 5555,
13
+ :epoll => false,
14
+ :gossip_interval => 1,
15
+ :redis => 6379,
16
+ :logging => true,
17
+ :level => :info
18
+ }.freeze
19
+
20
+ end
21
+ end
@@ -0,0 +1,77 @@
1
+ # lib/pokan-cluster/configuration.rb
2
+
3
+ module Pokan
4
+ module Cluster
5
+
6
+ # Class used to hold configuration options for anything.
7
+ # It inherits from Yell::Hash, so that the options are can be passed
8
+ # in a usual Ruby hash. Optionally, you can pass it a string containing
9
+ # your data and an object which responds to the method +parse+,
10
+ # which will take the data string and build the corresponding hash.
11
+ #
12
+ # The options passed can be accessed later via the +get+ method, used in the
13
+ # example above (see further details in Pokan::Cluster::Configuration#get)
14
+ #
15
+ # Usage
16
+ #
17
+ # # If we wanted to use a YAML configuration file...
18
+ # # and our file was the following:
19
+ # # pokan:
20
+ # # cluster: "configuration"
21
+ #
22
+ # class YAMLParser
23
+ # def self.parse(data)
24
+ # YAML.load(data)
25
+ # end
26
+ # end
27
+ #
28
+ # c = Pokan::Cluster::Configuration.new(File.read('config.yaml'), YAMLParser)
29
+ # c.get('pokan.cluster') #=> "configuration"
30
+ class Configuration < Yell::Hash
31
+
32
+ # Creates a new Pokan::Cluster::Configuration object, containing the
33
+ # data passed. It can be either a usual Ruby hash or a String with your
34
+ # data, as long as you provide a parser which responds to the method
35
+ # +parse+ taking the string as parameter and returning the corresponding
36
+ # hash.
37
+ def initialize(data, parser=nil)
38
+ case data
39
+ when Object::Hash
40
+ @data = data
41
+ when String
42
+ @data = load_data(data, parser)
43
+ end
44
+
45
+ super(@data)
46
+ end
47
+
48
+ # Loads the data in case a string was passed in the initialization.
49
+ # It actually just calls the method +parse+ on the parser object
50
+ def load_data(data, parser)
51
+ parser.parse(data)
52
+ end
53
+
54
+ # Main method, used to query values from your configuration. It accepts
55
+ # as a parameter a string containing the "path" to reach the data you
56
+ # want.
57
+ #
58
+ # h = { :max_connections => "10", :proto => { :tcp => true } }
59
+ # c = Pokan::Cluster::Configuration.new(h)
60
+ # c.get('max_connections') #=> "10"
61
+ # c.get('proto.tcp') #=> "true"
62
+ def get(query)
63
+ step = nil # contains consequent results of our query
64
+ path = query.split '.'
65
+
66
+ path.each { |param| step = send(param.to_sym) }
67
+
68
+ step
69
+ rescue NoMethodError
70
+ nil
71
+ ensure
72
+ reload
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ module Pokan
2
+ module Cluster
3
+
4
+ def self.m(string)
5
+ PreparedString.new(string)
6
+ end
7
+
8
+ LOG_MESSAGES = {
9
+ :start => { :debug => m("Address: %p\nTCP port: %p\nUDP port: %p\n
10
+ Using epoll: %p\nGossip interval: %p seconds\n
11
+ Seed address: %p\n\n
12
+ Log level is %p\n. Logging to %p\n"),
13
+
14
+ :info => m('Server is starting at %p...')
15
+ },
16
+
17
+ :before_sync => { :debug => m("Starting Redis sync with seed on address %p, port %p"),
18
+ :info => m("Syncing databases...")
19
+ },
20
+
21
+ :after_sync => { :debug => m("Databases synced in %p seconds"),
22
+ :info => m("Done.")
23
+ },
24
+
25
+ :new_key => { :debug => m("Saving new key-value pair: %p:%p") },
26
+
27
+ :new_peer => { :debug => m("New peer detected at address %p:%p"),
28
+ :info => m("New peer: %p") },
29
+
30
+ :key_changed => { :debug => m("Key %p changing from %p to %p") },
31
+
32
+ :key_removed => { :debug => m("Key %p was removed"),
33
+ :info => m("Peer at addres %p just left")
34
+ },
35
+
36
+ :before_gossip => { :debug => m("Preparing digest message for gossiping with %p (length: %p bytes)"),
37
+ :info => m("Sending gossip message to randomly selected peer (%p)")
38
+ },
39
+
40
+ :after_gossip => { :debug => m("Gossip message sent") },
41
+
42
+ :shutdown => m('Server is finishing...')
43
+
44
+ }.freeze
45
+
46
+
47
+ end
48
+ end
@@ -0,0 +1,59 @@
1
+ module Pokan
2
+
3
+ module Cluster
4
+
5
+ # PreparedString is a class representing strings with marks, to be
6
+ # substituted later with specific values.
7
+ #
8
+ # Example
9
+ #
10
+ # p = PreparedString.new('Hi %p, my name is %p')
11
+ # p.with('John', 'James').string #=> "Hi John, my name is James"
12
+ # p.string #=> "Hi %p, my name is %p"
13
+ # p.with('John').string #=> "Hi John, my name is %p"
14
+ #
15
+ # Useful for log messages.
16
+ class PreparedString
17
+
18
+ # Creates a new instance of your prepared string as the first argument.
19
+ # You can specify the token to be substituted when using PreparedString#with
20
+ # as the second argument. Default is `%p`
21
+ def initialize(string, token='%p')
22
+ @original, @token = string, token
23
+ @current = copy @original
24
+ @changed = false
25
+ end
26
+
27
+ # Retrieves the string content for the prepared string. If a substitution had
28
+ # already been done (using PreparedString#with), then the substitued string
29
+ # will be returned. Otherwise, the original string is returned.
30
+ def string
31
+ r_string = @changed ? @current : @original
32
+ @changed = false
33
+ @current = copy @original
34
+
35
+ r_string
36
+ end
37
+
38
+ # Allows you to specify the strings to be placed in the tokens
39
+ def with(*args)
40
+ @changed = true
41
+
42
+ args.each { |token|
43
+ @current.sub!(@token, token.to_s)
44
+ }
45
+
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ def copy(obj)
52
+ obj.dup
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,7 @@
1
+ # lib/pokan-cluster/version.rb
2
+
3
+ module Pokan
4
+ module Cluster
5
+ VERSION = "0.1"
6
+ end
7
+ end
@@ -0,0 +1,119 @@
1
+ # lib/pokan-cluster/server_configuration.rb
2
+ module Pokan
3
+
4
+ ##
5
+ # Class used to store and manage server configuration.
6
+ # Should not be used by user code, just by the Pokan::Cluster
7
+ # class. It loads a YAML configuration file located in the user
8
+ # home directory or in the +.config+ directory or in +/etc/pokan.yml+.
9
+ # Optionally, a different config file can be passed.
10
+ #
11
+ # === Usage
12
+ # s = Pokan::ServerConfiguration.new("pokan.yml")
13
+ # s.tcp_port #=> 8888 (depending on your configuration options)
14
+ #
15
+ # A general configuration file has the following skeleton:
16
+ #
17
+ # pokan:
18
+ # connection:
19
+ # udp: 88885
20
+ # tcp: 7777
21
+ #
22
+ # seed: "10.10.10.8"
23
+ #
24
+ # log:
25
+ # enabled: true
26
+ # log_file: "/var/log/pokan.log"
27
+ class ServerConfiguration
28
+
29
+ YAML_ROOT = 'pokan'
30
+
31
+ # Creates a new instance of Pokan::ServerConfiguration.
32
+ # You can pass an optional parameter indicating the location
33
+ # of the configuration file (must be a valid YAML file)
34
+ #
35
+ # If no config file is passed, we first look if there is a +.pokan.yml+
36
+ # file in the user's home directory. If there isn't, then we search
37
+ # for a +.config/pokan/pokan.yml+. If none of these are found, then
38
+ # we set the default values (see Pokan::ServerConfiguration#set_defaults
39
+ # for further details)
40
+ def initialize(config_file=nil)
41
+ @config_file = config_file || search_config
42
+
43
+ @config_file.nil? ? set_defaults : load_config(@config_file)
44
+ end
45
+
46
+ # Searches for a valid YAML configuration file in +.pokan.yml+
47
+ # located in the user's home directory or in +.config/pokan/pokan.yml'+,
48
+ # or in +/etc/pokan.yml+.
49
+ # Returns the file name, if found, or +nil+ if no configuration file was
50
+ # found.
51
+ def search_config
52
+ home_file = File.absolute_path '~/.pokan.yml'
53
+ config_file = File.absolute_path '~/.config/pokan/pokan.yml'
54
+ etc_file = '/etc/pokan.yml'
55
+
56
+ [home_file, config_file, etc_file].each do |config|
57
+ return config if File.exists? config
58
+ end
59
+
60
+ nil
61
+ end
62
+
63
+ # Returns +true+ if a configuration file was found, or if the user
64
+ # explicitly passed a file.
65
+ def has_config_file?
66
+ !@config_file.nil?
67
+ end
68
+
69
+ # Returns the absolute path of the configuration file, if any.
70
+ def file_path
71
+ @config_file
72
+ end
73
+
74
+ # Returns +true+ if we are logging activities on the server
75
+ def logging?
76
+ @logging
77
+ end
78
+
79
+ def load_config(config_file)
80
+ raise "YAML file not found: #{config_file}" unless File.exists? config_file
81
+
82
+ @data = YAML.load_file(config_file)
83
+ define_methods_for(@data[YAML_ROOT].keys)
84
+ end
85
+
86
+ # Sets the default values for all the attributes that can be set
87
+ # with the configuration file. These are:
88
+ # * TCP port: 5555
89
+ # * UDP port: 5555
90
+ # * no seeds information
91
+ # * logger enabled
92
+ # * log_file ./pokan.log
93
+ def set_defaults
94
+ @tcp_port = 5555
95
+ @udp_port = 5555
96
+ @seeds = []
97
+ @logging = true
98
+ @log_file = './pokan.log'
99
+ end
100
+
101
+ private
102
+
103
+ # Defines methods in the singleton class for every key in the +keys+
104
+ # parameter
105
+ def define_methods_for(keys)
106
+ keys.each do |k|
107
+ singleton_class.send(:define_method, k) do
108
+ @data[YAML_ROOT][k]
109
+ end
110
+ end
111
+ end
112
+
113
+ def singleton_class
114
+ class << self; self; end
115
+ end
116
+
117
+ end
118
+
119
+ end
data/pokan.log ADDED
@@ -0,0 +1,10 @@
1
+ INFO pokan-cluster : New peer detected at address role
2
+ INFO pokan-cluster : Sending gossip message to randomly selected peer ()
3
+ INFO pokan-cluster : New peer detected at address role
4
+ INFO pokan-cluster : Server is starting at 192.168.229.197...
5
+ INFO pokan-cluster : New peer detected at address role
6
+ INFO pokan-cluster : Server is starting at 192.168.229.197...
7
+ INFO pokan-cluster : New peer detected at address role
8
+ INFO pokan-cluster : Server is starting at 192.168.229.197...
9
+ INFO pokan-cluster : Sending gossip message to randomly selected peer (10.10.10.8)
10
+ INFO pokan-cluster : Server is starting at 192.168.229.197...
@@ -0,0 +1,14 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Pokan::Cluster::Autorunner do
4
+
5
+ subject { Pokan::Cluster::Autorunner }
6
+
7
+ it 'should register classes that include Pokan::Cluster' do
8
+ class DummyServer
9
+ include Pokan::Cluster
10
+ end
11
+
12
+ subject.server.should eq DummyServer
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Pokan::Cluster::Configuration do
4
+
5
+ let(:yaml) {
6
+ <<-END
7
+ connection:
8
+ udp: 8888
9
+ tcp: 8888
10
+
11
+ seed:
12
+ address: "10.10.10.8"
13
+ redis: 6379
14
+
15
+ log:
16
+ enabled: true
17
+ log_file: "/var/log/pokan"
18
+ END
19
+ }
20
+
21
+ subject {
22
+ parser = double('parser')
23
+ parser.stub! :parse => YAML.load(yaml)
24
+ Pokan::Cluster::Configuration.new(yaml, parser)
25
+ }
26
+
27
+ it 'should retrieve all data' do
28
+ subject.get('connection.udp').should eq 8888
29
+ subject.get('connection.tcp').should eq 8888
30
+
31
+ subject.get('seed.address').should eq "10.10.10.8"
32
+ subject.get('seed.redis').should eq 6379
33
+
34
+ subject.get('log.enabled').should be_true
35
+ subject.get('log.log_file').should eq "/var/log/pokan"
36
+ end
37
+
38
+ it 'should return nil on non existent configuration' do
39
+ subject.get('log.levell').should be_nil
40
+ end
41
+
42
+
43
+
44
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Pokan::Cluster::PreparedString do
4
+
5
+ let(:string) {
6
+ 'A: %p, B: %p C:%p'
7
+ }
8
+
9
+ let(:prepared_string) {
10
+ Pokan::Cluster::PreparedString.new(string)
11
+ }
12
+
13
+ it 'should return the given string when no substitution is made' do
14
+ prepared_string.string.should eq string
15
+ end
16
+
17
+ it 'should return semi substituted string' do
18
+ prepared_string.with('dummy').string.should eq 'A: dummy, B: %p C:%p'
19
+ end
20
+
21
+ it 'should return fully substituted string' do
22
+ prepared_string.with('dummy', 'yummy', 'mummy').string.should eq 'A: dummy, B: yummy C:mummy'
23
+ end
24
+
25
+ it 'should return correctly substituted string when more arguments are passed' do
26
+ prepared_string.with('dummy', 'yummy', 'mummy', 'a', 'b', 'c').string.should eq 'A: dummy, B: yummy C:mummy'
27
+ end
28
+
29
+ it 'should return initial string after changes' do
30
+ prepared_string.with('dummy').string.should eq 'A: dummy, B: %p C:%p'
31
+ prepared_string.string.should eq string
32
+ end
33
+
34
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Pokan::Cluster do
4
+
5
+ subject {
6
+ class MyPokan
7
+ include Pokan::Cluster
8
+
9
+ config {
10
+ YAML.parse(<<-END
11
+ pokan:
12
+
13
+ address: "10.10.10.7"
14
+
15
+ connect:
16
+ tcp: 8888
17
+ udp: 8888
18
+ epoll: true
19
+
20
+ seed:
21
+ address: "10.10.10.1"
22
+ redis: 6379
23
+
24
+ gossip:
25
+ every: 2
26
+
27
+ log:
28
+ enabled: true
29
+ level: "info"
30
+ file: "pokan.log"
31
+ END
32
+ ).to_ruby
33
+ }
34
+
35
+ self
36
+ end
37
+ }
38
+
39
+ context 'when all options are passed' do
40
+
41
+ it 'should properly initialize instance variables' do
42
+ subject.address.should eq '10.10.10.7'
43
+
44
+ subject.tcp_port.should eq 8888
45
+ subject.udp_port.should eq 8888
46
+ subject.epoll.should be_true
47
+
48
+ subject.seed.should eq '10.10.10.1'
49
+
50
+ subject.gossip_interval.should eq 2
51
+
52
+ subject.logging.should be_true
53
+ subject.log_level.should eq :info
54
+ subject.log_file.should eq 'pokan.log'
55
+
56
+ end
57
+
58
+ it 'should delegate to the server instance' do
59
+ subject.use_epoll.should be_true
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'pokan-cluster'
data/test_server.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'pokan-cluster'
2
+ require 'yaml'
3
+
4
+ class TestServer
5
+ include Pokan::Cluster
6
+
7
+ config { YAML.parse_file('/home/renato/pokan.yml').to_ruby }
8
+
9
+ after :sync do
10
+ puts "Synced!"
11
+ end
12
+
13
+ after :gossip do |addr, port|
14
+ puts "Gossiped with #{addr}:#{port}"
15
+ end
16
+
17
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pokan-cluster
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Fabio Lima Pereira
9
+ - Rafael Regis do Prado
10
+ - Renato Mascarenhas
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2011-11-14 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: pokan
18
+ requirement: &4783360 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *4783360
27
+ - !ruby/object:Gem::Dependency
28
+ name: yagni
29
+ requirement: &4782660 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *4782660
38
+ - !ruby/object:Gem::Dependency
39
+ name: host
40
+ requirement: &4781960 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *4781960
49
+ description: ! " pokan-cluster adds a handy set of functionalities on top of pokan,
50
+ everyone's\n favorite Ruby Gossip protocol implementation. Logging, easy YAML
51
+ configuration\n file, workload information of peers in your network (so that
52
+ you can write a\n simple load balancer) are among the features pokan-cluster
53
+ has to offer!\n Take a look at it and contribute! (see our Github page).\n"
54
+ email: renato.mascosta@gmail.com
55
+ executables: []
56
+ extensions: []
57
+ extra_rdoc_files: []
58
+ files:
59
+ - .document
60
+ - .rspec
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - Guardfile
64
+ - LICENSE.txt
65
+ - README.rdoc
66
+ - Rakefile
67
+ - lib/pokan-cluster.rb
68
+ - lib/pokan-cluster/autorunner.rb
69
+ - lib/pokan-cluster/config_defaults.rb
70
+ - lib/pokan-cluster/configuration.rb
71
+ - lib/pokan-cluster/log_messages.rb
72
+ - lib/pokan-cluster/prepared_string.rb
73
+ - lib/pokan-cluster/version.rb
74
+ - lib/server_configuration.rb
75
+ - pokan.log
76
+ - spec/pokan-cluster/autorunner_spec.rb
77
+ - spec/pokan-cluster/configuration_spec.rb
78
+ - spec/pokan-cluster/prepared_string_spec.rb
79
+ - spec/pokan-cluster_spec.rb
80
+ - spec/spec_helper.rb
81
+ - test_server.rb
82
+ homepage: http://github.com/rmascarenhas/pokan-cluster
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ segments:
96
+ - 0
97
+ hash: 1258949825256661048
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.10
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: pokan-cluster is a set of neat, handy functionalities built on top of pokan.
110
+ test_files: []