beetle 0.1 → 0.2.0

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.
Files changed (48) hide show
  1. data/README.rdoc +18 -8
  2. data/beetle.gemspec +37 -121
  3. data/bin/beetle +9 -0
  4. data/examples/README.rdoc +0 -2
  5. data/examples/rpc.rb +3 -2
  6. data/ext/mkrf_conf.rb +19 -0
  7. data/lib/beetle/base.rb +1 -8
  8. data/lib/beetle/client.rb +16 -14
  9. data/lib/beetle/commands/configuration_client.rb +73 -0
  10. data/lib/beetle/commands/configuration_server.rb +85 -0
  11. data/lib/beetle/commands.rb +30 -0
  12. data/lib/beetle/configuration.rb +70 -7
  13. data/lib/beetle/deduplication_store.rb +50 -38
  14. data/lib/beetle/handler.rb +2 -5
  15. data/lib/beetle/logging.rb +7 -0
  16. data/lib/beetle/message.rb +11 -13
  17. data/lib/beetle/publisher.rb +2 -2
  18. data/lib/beetle/r_c.rb +2 -1
  19. data/lib/beetle/redis_configuration_client.rb +136 -0
  20. data/lib/beetle/redis_configuration_server.rb +301 -0
  21. data/lib/beetle/redis_ext.rb +79 -0
  22. data/lib/beetle/redis_master_file.rb +35 -0
  23. data/lib/beetle/redis_server_info.rb +65 -0
  24. data/lib/beetle/subscriber.rb +4 -1
  25. data/lib/beetle.rb +2 -2
  26. data/test/beetle/configuration_test.rb +14 -2
  27. data/test/beetle/deduplication_store_test.rb +61 -43
  28. data/test/beetle/message_test.rb +28 -4
  29. data/test/beetle/redis_configuration_client_test.rb +97 -0
  30. data/test/beetle/redis_configuration_server_test.rb +278 -0
  31. data/test/beetle/redis_ext_test.rb +71 -0
  32. data/test/beetle/redis_master_file_test.rb +39 -0
  33. data/test/test_helper.rb +13 -1
  34. metadata +59 -50
  35. data/.gitignore +0 -5
  36. data/MIT-LICENSE +0 -20
  37. data/Rakefile +0 -114
  38. data/TODO +0 -7
  39. data/doc/redundant_queues.graffle +0 -7744
  40. data/etc/redis-master.conf +0 -189
  41. data/etc/redis-slave.conf +0 -189
  42. data/examples/redis_failover.rb +0 -65
  43. data/script/start_rabbit +0 -29
  44. data/snafu.rb +0 -55
  45. data/test/beetle/bla.rb +0 -0
  46. data/test/beetle.yml +0 -81
  47. data/tmp/master/.gitignore +0 -2
  48. data/tmp/slave/.gitignore +0 -3
data/README.rdoc CHANGED
@@ -18,9 +18,9 @@ More information can be found on the {project website}[http://xing.github.com/be
18
18
  === Configuration
19
19
  # configure machines
20
20
 
21
- Beetle.config do |c|
21
+ Beetle.config do |config|
22
22
  config.servers = "broker1:5672, broker2:5672"
23
- config.redis_hosts = "redis1:6379, redis2:6379"
23
+ config.redis_server = "redis1:6379"
24
24
  end
25
25
 
26
26
  # instantiate a beetle client
@@ -41,7 +41,19 @@ More information can be found on the {project website}[http://xing.github.com/be
41
41
  === Subscribing
42
42
  b.listen
43
43
 
44
- :include: examples/README.rdoc
44
+ === Examples
45
+
46
+ Beetle ships with a number of {example scripts}[http://github.com/xing/beetle/tree/master/examples/].
47
+
48
+ The top level Rakefile comes with targets to start several RabbitMQ and redis instances
49
+ locally. Make sure the corresponding binaries are in your search path. Open four new shell
50
+ windows and execute the following commands:
51
+
52
+ rake rabbit:start1
53
+ rake rabbit:start2
54
+ rake redis:start1
55
+ rake redis:start2
56
+
45
57
 
46
58
  == Prerequisites
47
59
 
@@ -68,8 +80,9 @@ For development, you'll need
68
80
  == Authors
69
81
 
70
82
  {Stefan Kaes}[http://github.com/skaes],
71
- {Pascal Friederich}[http://github.com/paukul] and
72
- {Ali Jelveh}[http://github.com/dudemeister].
83
+ {Pascal Friederich}[http://github.com/paukul],
84
+ {Ali Jelveh}[http://github.com/dudemeister] and
85
+ {Sebastian Roebke}[http://github.com/boosty].
73
86
 
74
87
  You cand find out more about our work on our {dev blog}[http://devblog.xing.com].
75
88
 
@@ -77,6 +90,3 @@ Copyright (c) 2010 {XING AG}[http://www.xing.com/]
77
90
 
78
91
  Released under the MIT license. For full details see MIT-LICENSE included in this
79
92
  distribution.
80
-
81
-
82
-
data/beetle.gemspec CHANGED
@@ -1,127 +1,43 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
5
-
6
1
  Gem::Specification.new do |s|
7
- s.name = %q{beetle}
8
- s.version = "0.1"
2
+ s.name = "beetle"
3
+ s.version = "0.2.0"
4
+
5
+ s.required_rubygems_version = ">= 1.3.1"
6
+ s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh", "Sebastian Roebke"]
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.default_executable = "beetle"
9
+ s.description = "A highly available, reliable messaging infrastructure"
10
+ s.summary = "High Availability AMQP Messaging with Redundant Queues"
11
+ s.email = "developers@xing.com"
12
+ s.executables = ["beetle"]
13
+ s.extra_rdoc_files = ["README.rdoc"]
14
+ s.files = Dir['{examples,ext,lib}/**/*.rb'] + %w(beetle.gemspec examples/README.rdoc)
15
+ s.extensions = 'ext/mkrf_conf.rb'
16
+ s.homepage = "http://xing.github.com/beetle/"
17
+ s.rdoc_options = ["--charset=UTF-8"]
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = "1.3.7"
20
+ s.test_files = Dir['test/**/*.rb']
21
+
22
+ s.post_install_message = <<-INFO
23
+ *********************************************************************************************
9
24
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh"]
12
- s.date = %q{2010-04-14}
13
- s.description = %q{A highly available, reliable messaging infrastructure}
14
- s.email = %q{developers@xing.com}
15
- s.extra_rdoc_files = [
16
- "README.rdoc",
17
- "TODO"
18
- ]
19
- s.files = [
20
- ".gitignore",
21
- "MIT-LICENSE",
22
- "README.rdoc",
23
- "Rakefile",
24
- "TODO",
25
- "beetle.gemspec",
26
- "doc/redundant_queues.graffle",
27
- "etc/redis-master.conf",
28
- "etc/redis-slave.conf",
29
- "examples/README.rdoc",
30
- "examples/attempts.rb",
31
- "examples/handler_class.rb",
32
- "examples/handling_exceptions.rb",
33
- "examples/multiple_exchanges.rb",
34
- "examples/multiple_queues.rb",
35
- "examples/redis_failover.rb",
36
- "examples/redundant.rb",
37
- "examples/rpc.rb",
38
- "examples/simple.rb",
39
- "lib/beetle.rb",
40
- "lib/beetle/base.rb",
41
- "lib/beetle/client.rb",
42
- "lib/beetle/configuration.rb",
43
- "lib/beetle/deduplication_store.rb",
44
- "lib/beetle/handler.rb",
45
- "lib/beetle/message.rb",
46
- "lib/beetle/publisher.rb",
47
- "lib/beetle/r_c.rb",
48
- "lib/beetle/subscriber.rb",
49
- "script/start_rabbit",
50
- "snafu.rb",
51
- "test/beetle.yml",
52
- "test/beetle/base_test.rb",
53
- "test/beetle/bla.rb",
54
- "test/beetle/client_test.rb",
55
- "test/beetle/configuration_test.rb",
56
- "test/beetle/deduplication_store_test.rb",
57
- "test/beetle/handler_test.rb",
58
- "test/beetle/message_test.rb",
59
- "test/beetle/publisher_test.rb",
60
- "test/beetle/r_c_test.rb",
61
- "test/beetle/subscriber_test.rb",
62
- "test/beetle_test.rb",
63
- "test/test_helper.rb",
64
- "tmp/master/.gitignore",
65
- "tmp/slave/.gitignore"
66
- ]
67
- s.homepage = %q{http://xing.github.com/beetle/}
68
- s.rdoc_options = ["--charset=UTF-8"]
69
- s.require_paths = ["lib"]
70
- s.rubygems_version = %q{1.3.5}
71
- s.summary = %q{High Availability AMQP Messaging with Redundant Queues}
72
- s.test_files = [
73
- "test/beetle/base_test.rb",
74
- "test/beetle/bla.rb",
75
- "test/beetle/client_test.rb",
76
- "test/beetle/configuration_test.rb",
77
- "test/beetle/deduplication_store_test.rb",
78
- "test/beetle/handler_test.rb",
79
- "test/beetle/message_test.rb",
80
- "test/beetle/publisher_test.rb",
81
- "test/beetle/r_c_test.rb",
82
- "test/beetle/subscriber_test.rb",
83
- "test/beetle_test.rb",
84
- "test/test_helper.rb",
85
- "examples/attempts.rb",
86
- "examples/handler_class.rb",
87
- "examples/handling_exceptions.rb",
88
- "examples/multiple_exchanges.rb",
89
- "examples/multiple_queues.rb",
90
- "examples/redis_failover.rb",
91
- "examples/redundant.rb",
92
- "examples/rpc.rb",
93
- "examples/simple.rb"
94
- ]
25
+ If you're running a ruby version < 1.9 we silently installed the SystemTimer gem for you.
26
+ See: http://ph7spot.com/musings/system-timer
95
27
 
96
- if s.respond_to? :specification_version then
97
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
98
- s.specification_version = 3
28
+ *********************************************************************************************
29
+ INFO
99
30
 
100
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
101
- s.add_runtime_dependency(%q<uuid4r>, [">= 0.1.1"])
102
- s.add_runtime_dependency(%q<bunny>, [">= 0.6.0"])
103
- s.add_runtime_dependency(%q<redis>, [">= 0.1.2"])
104
- s.add_runtime_dependency(%q<amqp>, [">= 0.6.7"])
105
- s.add_runtime_dependency(%q<activesupport>, [">= 2.3.4"])
106
- s.add_development_dependency(%q<mocha>, [">= 0"])
107
- s.add_development_dependency(%q<rcov>, [">= 0"])
108
- else
109
- s.add_dependency(%q<uuid4r>, [">= 0.1.1"])
110
- s.add_dependency(%q<bunny>, [">= 0.6.0"])
111
- s.add_dependency(%q<redis>, [">= 0.1.2"])
112
- s.add_dependency(%q<amqp>, [">= 0.6.7"])
113
- s.add_dependency(%q<activesupport>, [">= 2.3.4"])
114
- s.add_dependency(%q<mocha>, [">= 0"])
115
- s.add_dependency(%q<rcov>, [">= 0"])
116
- end
117
- else
118
- s.add_dependency(%q<uuid4r>, [">= 0.1.1"])
119
- s.add_dependency(%q<bunny>, [">= 0.6.0"])
120
- s.add_dependency(%q<redis>, [">= 0.1.2"])
121
- s.add_dependency(%q<amqp>, [">= 0.6.7"])
122
- s.add_dependency(%q<activesupport>, [">= 2.3.4"])
123
- s.add_dependency(%q<mocha>, [">= 0"])
124
- s.add_dependency(%q<rcov>, [">= 0"])
125
- end
31
+ s.specification_version = 3
32
+ s.add_runtime_dependency("uuid4r", [">= 0.1.1"])
33
+ s.add_runtime_dependency("bunny", [">= 0.6.0"])
34
+ s.add_runtime_dependency("redis", [">= 2.0.3"])
35
+ s.add_runtime_dependency("amqp", [">= 0.6.7"])
36
+ s.add_runtime_dependency("activesupport", [">= 2.3.4"])
37
+ s.add_runtime_dependency("daemons", [">= 1.0.10"])
38
+ s.add_development_dependency("mocha", [">= 0"])
39
+ s.add_development_dependency("rcov", [">= 0"])
40
+ s.add_development_dependency("cucumber", [">= 0.7.2"])
41
+ s.add_development_dependency("daemon_controller", [">= 0"])
126
42
  end
127
43
 
data/bin/beetle ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'beetle/commands'
5
+ rescue LoadError
6
+ beetle_path = File.expand_path("../../lib", __FILE__)
7
+ $:.unshift(beetle_path)
8
+ require 'beetle/commands'
9
+ end
data/examples/README.rdoc CHANGED
@@ -10,5 +10,3 @@ windows and execute the following commands:
10
10
  rake rabbit:start2
11
11
  rake redis:start1
12
12
  rake redis:start2
13
-
14
- After running the redis_failover.rb script you will need to restart both redis servers.
data/examples/rpc.rb CHANGED
@@ -4,9 +4,10 @@ require File.expand_path(File.dirname(__FILE__)+"/../lib/beetle")
4
4
 
5
5
  # suppress debug messages
6
6
  Beetle.config.logger.level = Logger::DEBUG
7
-
7
+ Beetle.config.servers = "localhost:5672, localhost:5673"
8
8
  # instantiate a client
9
- client = Beetle::Client.new(:servers => "localhost:5672, localhost:5673")
9
+
10
+ client = Beetle::Client.new
10
11
 
11
12
  # register a durable queue named 'test'
12
13
  # this implicitly registers a durable topic exchange called 'test'
data/ext/mkrf_conf.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'rubygems/command.rb'
3
+ require 'rubygems/dependency_installer.rb'
4
+ begin
5
+ Gem::Command.build_args = ARGV
6
+ rescue NoMethodError
7
+ end
8
+ inst = Gem::DependencyInstaller.new
9
+ begin
10
+ if RUBY_VERSION < "1.9"
11
+ inst.install "SystemTimer", ">= 1.2"
12
+ end
13
+ rescue
14
+ exit(1)
15
+ end
16
+
17
+ f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w") # create dummy rakefile to indicate success
18
+ f.write("task :default\n")
19
+ f.close
data/lib/beetle/base.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Beetle
2
2
  # Abstract base class shared by Publisher and Subscriber
3
3
  class Base
4
+ include Logging
4
5
 
5
6
  attr_accessor :options, :servers, :server #:nodoc:
6
7
 
@@ -15,14 +16,6 @@ module Beetle
15
16
 
16
17
  private
17
18
 
18
- def logger
19
- self.class.logger
20
- end
21
-
22
- def self.logger
23
- Beetle.config.logger
24
- end
25
-
26
19
  def error(text)
27
20
  logger.error text
28
21
  raise Error.new(text)
data/lib/beetle/client.rb CHANGED
@@ -19,6 +19,8 @@ module Beetle
19
19
  # order, so that no message is lost if message producers are accidentally started before
20
20
  # the corresponding consumers.
21
21
  class Client
22
+ include Logging
23
+
22
24
  # the AMQP servers available for publishing
23
25
  attr_reader :servers
24
26
 
@@ -37,14 +39,18 @@ module Beetle
37
39
  # the deduplication store to use for this client
38
40
  attr_reader :deduplication_store
39
41
 
42
+ # accessor for the beetle configuration
43
+ attr_reader :config
44
+
40
45
  # create a fresh Client instance from a given configuration object
41
46
  def initialize(config = Beetle.config)
47
+ @config = config
42
48
  @servers = config.servers.split(/ *, */)
43
49
  @exchanges = {}
44
50
  @queues = {}
45
51
  @messages = {}
46
52
  @bindings = {}
47
- @deduplication_store = DeduplicationStore.new(config.redis_hosts, config.redis_db)
53
+ @deduplication_store = DeduplicationStore.new(config)
48
54
  end
49
55
 
50
56
  # register an exchange with the given _name_ and a set of _options_:
@@ -69,8 +75,8 @@ module Beetle
69
75
  def register_queue(name, options={})
70
76
  name = name.to_s
71
77
  raise ConfigurationError.new("queue #{name} already configured") if queues.include?(name)
72
- opts = {:exchange => name, :key => name}.merge!(options.symbolize_keys)
73
- opts.merge! :durable => true, :passive => false, :exclusive => false, :auto_delete => false, :amqp_name => name
78
+ opts = {:exchange => name, :key => name, :auto_delete => false, :amqp_name => name}.merge!(options.symbolize_keys)
79
+ opts.merge! :durable => true, :passive => false, :exclusive => false
74
80
  exchange = opts.delete(:exchange).to_s
75
81
  key = opts.delete(:key)
76
82
  queues[name] = opts
@@ -139,10 +145,10 @@ module Beetle
139
145
 
140
146
  # this is a convenience method to configure exchanges, queues, messages and handlers
141
147
  # with a common set of options. allows one to call all register methods without the
142
- # register_ prefix.
148
+ # register_ prefix. returns self.
143
149
  #
144
150
  # Example:
145
- # client.configure :exchange => :foobar do |config|
151
+ # client = Beetle.client.new.configure :exchange => :foobar do |config|
146
152
  # config.queue :q1, :key => "foo"
147
153
  # config.queue :q2, :key => "bar"
148
154
  # config.message :foo
@@ -152,6 +158,7 @@ module Beetle
152
158
  # end
153
159
  def configure(options={}) #:yields: config
154
160
  yield Configurator.new(self, options)
161
+ self
155
162
  end
156
163
 
157
164
  # publishes a message. the given options hash is merged with options given on message registration.
@@ -195,10 +202,10 @@ module Beetle
195
202
  publisher.stop
196
203
  end
197
204
 
198
- # traces all messages received on all queues. useful for debugging message flow.
199
- def trace(&block)
205
+ # traces messages without consuming them. useful for debugging message flow.
206
+ def trace(messages=self.messages.keys, &block)
200
207
  queues.each do |name, opts|
201
- opts.merge! :durable => false, :auto_delete => true, :amqp_name => queue_name_for_tracing(name)
208
+ opts.merge! :durable => false, :auto_delete => true, :amqp_name => queue_name_for_tracing(opts[:amqp_name])
202
209
  end
203
210
  register_handler(queues.keys) do |msg|
204
211
  puts "-----===== new message =====-----"
@@ -207,7 +214,7 @@ module Beetle
207
214
  puts "MSGID: #{msg.msg_id}"
208
215
  puts "DATA: #{msg.data}"
209
216
  end
210
- subscriber.listen(messages.keys, &block)
217
+ listen(messages, &block)
211
218
  end
212
219
 
213
220
  # evaluate the ruby files matching the given +glob+ pattern in the context of the client instance.
@@ -218,11 +225,6 @@ module Beetle
218
225
  end
219
226
  end
220
227
 
221
- # returns the configured Logger instance
222
- def logger
223
- @logger ||= Beetle.config.logger
224
- end
225
-
226
228
  private
227
229
 
228
230
  class Configurator #:nodoc:all
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+ require 'daemons'
3
+ require 'beetle'
4
+
5
+ module Beetle
6
+ module Commands
7
+ # Command to start a RedisConfigurationClient daemon.
8
+ #
9
+ # Usage: beetle configuration_client [options] -- [client options]
10
+ #
11
+ # client options:
12
+ # --redis-master-file FILE Write redis master server string to FILE
13
+ # --id, --client-id ID Set unique client id (default is minastirith.local)
14
+ # --amqp-servers LIST AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)
15
+ # --config-file PATH Path to an external yaml config file
16
+ # --pid-dir DIR Write pid and log to DIR
17
+ # -v, --verbose Set log level to DEBUG
18
+ # -h, --help Show this message
19
+ #
20
+ class ConfigurationClient
21
+ # parses command line options and starts Beetle::RedisConfigurationClient as a daemon
22
+ def self.execute
23
+ command, controller_options, app_options = Daemons::Controller.split_argv(ARGV)
24
+
25
+ opts = OptionParser.new
26
+ opts.banner = "Usage: beetle configuration_client #{command} [options] -- [client options]"
27
+ opts.separator ""
28
+ opts.separator "client options:"
29
+
30
+ opts.on("--redis-master-file FILE", String, "Write redis master server string to FILE") do |val|
31
+ Beetle.config.redis_server = val
32
+ end
33
+
34
+ client_id = nil
35
+ opts.on("--id ID", "--client-id ID", String, "Set unique client id (default is #{RedisConfigurationClient.new.id})") do |val|
36
+ client_id = val
37
+ end
38
+
39
+ opts.on("--amqp-servers LIST", String, "AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)") do |val|
40
+ Beetle.config.servers = val
41
+ end
42
+
43
+ opts.on("--config-file PATH", String, "Path to an external yaml config file") do |val|
44
+ Beetle.config.config_file = val
45
+ end
46
+
47
+ dir_mode = nil
48
+ dir = nil
49
+ opts.on("--pid-dir DIR", String, "Write pid and log to DIR") do |val|
50
+ dir_mode = :normal
51
+ dir = val
52
+ end
53
+
54
+ opts.on("-v", "--verbose", "Set log level to DEBUG") do |val|
55
+ Beetle.config.logger.level = Logger::DEBUG
56
+ end
57
+
58
+ opts.on_tail("-h", "--help", "Show this message") do
59
+ puts opts
60
+ exit
61
+ end
62
+
63
+ opts.parse!(app_options)
64
+
65
+ Daemons.run_proc("redis_configuration_client", :multiple => true, :log_output => true, :dir_mode => dir_mode, :dir => dir) do
66
+ client = Beetle::RedisConfigurationClient.new
67
+ client.id = client_id if client_id
68
+ client.start
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,85 @@
1
+ require 'optparse'
2
+ require 'daemons'
3
+ require 'beetle'
4
+
5
+ module Beetle
6
+ module Commands
7
+ # Command to start a RedisConfigurationServer daemon.
8
+ #
9
+ # Usage: beetle configuration_server [options] -- [server options]
10
+ #
11
+ # server options:
12
+ # --redis-servers LIST Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)
13
+ # --client-ids LIST Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)
14
+ # --redis-master-file FILE Write redis master server string to FILE
15
+ # --redis-retry-interval SEC Number of seconds to wait between master checks
16
+ # --amqp-servers LIST AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)
17
+ # --config-file PATH Path to an external yaml config file
18
+ # --pid-dir DIR Write pid and log to DIR
19
+ # -v, --verbose
20
+ # -h, --help Show this message
21
+ #
22
+ class ConfigurationServer
23
+ # parses command line options and starts Beetle::RedisConfigurationServer as a daemon
24
+ def self.execute
25
+ command, controller_options, app_options = Daemons::Controller.split_argv(ARGV)
26
+
27
+ opts = OptionParser.new
28
+ opts.banner = "Usage: beetle configuration_server #{command} [options] -- [server options]"
29
+ opts.separator ""
30
+ opts.separator "server options:"
31
+
32
+ opts.on("--redis-servers LIST", Array, "Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)") do |val|
33
+ Beetle.config.redis_servers = val.join(",")
34
+ end
35
+
36
+ opts.on("--client-ids LIST", "Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)") do |val|
37
+ Beetle.config.redis_configuration_client_ids = val
38
+ end
39
+
40
+ opts.on("--redis-master-file FILE", String, "Write redis master server string to FILE") do |val|
41
+ Beetle.config.redis_server = val
42
+ end
43
+
44
+ opts.on("--redis-retry-interval SEC", Integer, "Number of seconds to wait between master checks") do |val|
45
+ Beetle.config.redis_configuration_master_retry_interval = val
46
+ end
47
+
48
+ opts.on("--amqp-servers LIST", String, "AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)") do |val|
49
+ Beetle.config.servers = val
50
+ end
51
+
52
+ opts.on("--config-file PATH", String, "Path to an external yaml config file") do |val|
53
+ Beetle.config.config_file = val
54
+ end
55
+
56
+ dir_mode = nil
57
+ dir = nil
58
+ opts.on("--pid-dir DIR", String, "Write pid and log to DIR") do |val|
59
+ dir_mode = :normal
60
+ dir = val
61
+ end
62
+
63
+ opts.on("-v", "--verbose") do |val|
64
+ Beetle.config.logger.level = Logger::DEBUG
65
+ end
66
+
67
+ opts.on_tail("-h", "--help", "Show this message") do
68
+ puts opts
69
+ exit
70
+ end
71
+
72
+ opts.parse!(app_options)
73
+
74
+ if command =~ /start|run/ && Beetle.config.redis_servers.blank?
75
+ puts opts
76
+ exit
77
+ end
78
+
79
+ Daemons.run_proc("redis_configuration_server", :log_output => true, :dir_mode => dir_mode, :dir => dir) do
80
+ Beetle::RedisConfigurationServer.new.start
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+
4
+ module Beetle
5
+ module Commands
6
+ # invokes given command by instantiating an appropriate command class
7
+ def self.execute(command)
8
+ if commands.include? command
9
+ require File.expand_path("../commands/#{command}", __FILE__)
10
+ "Beetle::Commands::#{command.classify}".constantize.execute
11
+ else
12
+ # me no likez no frikin heredocs
13
+ puts "\nCommand #{command} not known\n" if command
14
+ puts "Available commands are:"
15
+ puts
16
+ commands.each {|c| puts "\t #{c}"}
17
+ puts
18
+ exit 1
19
+ end
20
+ end
21
+
22
+ private
23
+ def self.commands
24
+ commands_dir = File.expand_path('../commands', __FILE__)
25
+ Dir[commands_dir + '/*.rb'].map {|f| File.basename(f)[0..-4]}
26
+ end
27
+ end
28
+ end
29
+
30
+ Beetle::Commands.execute(ARGV.shift)
@@ -1,31 +1,94 @@
1
+ require 'erb'
2
+
1
3
  module Beetle
2
4
  class Configuration
5
+ # system name (used for redis cluster partitioning) (defaults to <tt>system</tt>)
6
+ attr_accessor :system_name
3
7
  # default logger (defaults to <tt>Logger.new(STDOUT)</tt>)
4
8
  attr_accessor :logger
5
9
  # number of seconds after which keys are removed form the message deduplication store (defaults to <tt>3.days</tt>)
6
10
  attr_accessor :gc_threshold
7
- # the machines where the deduplication store lives (defaults to <tt>"localhost:6379"</tt>)
8
- attr_accessor :redis_hosts
11
+ # the redis server to use for deduplication
12
+ # either a string like <tt>"localhost:6379"</tt> (default) or a file that contains the string.
13
+ # use a file if you are using a beetle configuration_client process to update it for automatic redis failover.
14
+ attr_accessor :redis_server
15
+ # comma separated list of redis servers available for master/slave switching
16
+ # e.g. "192.168.1.2:6379,192.168.1.3:6379"
17
+ attr_accessor :redis_servers
9
18
  # redis database number to use for the message deduplication store (defaults to <tt>4</tt>)
10
19
  attr_accessor :redis_db
20
+
21
+ # how long we should repeatedly retry a redis operation before giving up, with a one
22
+ # second sleep between retries (defaults to <tt>180.seconds</tt>). this value needs to be
23
+ # somewehere between the maximum time it takes to auto-switch redis and the smallest
24
+ # handler timeout.
25
+ attr_accessor :redis_failover_timeout
26
+
27
+ ## redis configuration server options
28
+ # how often should the redis configuration server try to reach the redis master before nominating a new one (defaults to <tt>3</tt>)
29
+ attr_accessor :redis_configuration_master_retries
30
+ # number of seconds to wait between retries (defaults to <tt>10</tt>)
31
+ attr_accessor :redis_configuration_master_retry_interval
32
+ # number of seconds the redis configuration server waits for answers from clients (defaults to <tt>5</tt>)
33
+ attr_accessor :redis_configuration_client_timeout
34
+ # the redis configuration client ids living on the worker machines taking part in the redis failover, separated by comma (defaults to <tt>""</tt>)
35
+ attr_accessor :redis_configuration_client_ids
36
+
11
37
  # list of amqp servers to use (defaults to <tt>"localhost:5672"</tt>)
12
38
  attr_accessor :servers
13
- # the virtual host to use on the AMQP servers
39
+ # the virtual host to use on the AMQP servers (defaults to <tt>"/"</tt>)
14
40
  attr_accessor :vhost
15
- # the AMQP user to use when connecting to the AMQP servers
41
+ # the AMQP user to use when connecting to the AMQP servers (defaults to <tt>"guest"</tt>)
16
42
  attr_accessor :user
17
- # the password to use when connectiong to the AMQP servers
43
+ # the password to use when connectiong to the AMQP servers (defaults to <tt>"guest"</tt>)
18
44
  attr_accessor :password
19
45
 
46
+ # external config file (defaults to <tt>no file</tt>)
47
+ attr_reader :config_file
48
+
20
49
  def initialize #:nodoc:
21
- self.logger = Logger.new(STDOUT)
50
+ self.system_name = "system"
51
+
52
+ self.logger = begin
53
+ logger = Logger.new(STDOUT)
54
+ logger.formatter = Logger::Formatter.new
55
+ logger.level = Logger::INFO
56
+ logger.datetime_format = "%Y-%m-%d %H:%M:%S"
57
+ logger
58
+ end
59
+
22
60
  self.gc_threshold = 3.days
23
- self.redis_hosts = "localhost:6379"
61
+ self.redis_server = "localhost:6379"
62
+ self.redis_servers = ""
24
63
  self.redis_db = 4
64
+ self.redis_failover_timeout = 180.seconds
65
+
66
+ self.redis_configuration_master_retries = 3
67
+ self.redis_configuration_master_retry_interval = 10.seconds
68
+ self.redis_configuration_client_timeout = 5.seconds
69
+ self.redis_configuration_client_ids = ""
70
+
25
71
  self.servers = "localhost:5672"
26
72
  self.vhost = "/"
27
73
  self.user = "guest"
28
74
  self.password = "guest"
29
75
  end
76
+
77
+ # setting the external config file will load it on assignment
78
+ def config_file=(file_name) #:nodoc:
79
+ @config_file = file_name
80
+ load_config
81
+ end
82
+
83
+ private
84
+ def load_config
85
+ hash = YAML::load(ERB.new(IO.read(config_file)).result)
86
+ hash.each do |key, value|
87
+ send("#{key}=", value)
88
+ end
89
+ rescue Exception
90
+ logger.error "Error loading beetle config file '#{config_file}': #{$!}"
91
+ raise
92
+ end
30
93
  end
31
94
  end