tochtli 0.5.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.
- checksums.yaml +7 -0
- data/.travis.yml +14 -0
- data/Gemfile +32 -0
- data/History.md +138 -0
- data/README.md +46 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/assets/communication.png +0 -0
- data/assets/layers.png +0 -0
- data/examples/01-screencap-service/Gemfile +3 -0
- data/examples/01-screencap-service/README.md +5 -0
- data/examples/01-screencap-service/client.rb +15 -0
- data/examples/01-screencap-service/common.rb +15 -0
- data/examples/01-screencap-service/server.rb +26 -0
- data/examples/02-log-analyzer/Gemfile +3 -0
- data/examples/02-log-analyzer/README.md +5 -0
- data/examples/02-log-analyzer/client.rb +95 -0
- data/examples/02-log-analyzer/common.rb +33 -0
- data/examples/02-log-analyzer/sample.log +10001 -0
- data/examples/02-log-analyzer/server.rb +133 -0
- data/lib/tochtli.rb +177 -0
- data/lib/tochtli/active_record_connection_cleaner.rb +9 -0
- data/lib/tochtli/application.rb +135 -0
- data/lib/tochtli/base_client.rb +135 -0
- data/lib/tochtli/base_controller.rb +360 -0
- data/lib/tochtli/controller_manager.rb +99 -0
- data/lib/tochtli/engine.rb +15 -0
- data/lib/tochtli/message.rb +114 -0
- data/lib/tochtli/rabbit_client.rb +36 -0
- data/lib/tochtli/rabbit_connection.rb +249 -0
- data/lib/tochtli/reply_queue.rb +129 -0
- data/lib/tochtli/simple_validation.rb +23 -0
- data/lib/tochtli/test.rb +9 -0
- data/lib/tochtli/test/client.rb +28 -0
- data/lib/tochtli/test/controller.rb +66 -0
- data/lib/tochtli/test/integration.rb +78 -0
- data/lib/tochtli/test/memory_cache.rb +22 -0
- data/lib/tochtli/test/test_case.rb +191 -0
- data/lib/tochtli/test/test_unit.rb +22 -0
- data/lib/tochtli/version.rb +3 -0
- data/log_generator.rb +11 -0
- data/test/base_client_test.rb +68 -0
- data/test/controller_functional_test.rb +87 -0
- data/test/controller_integration_test.rb +274 -0
- data/test/controller_manager_test.rb +75 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/config/application.rb +36 -0
- data/test/dummy/config/boot.rb +4 -0
- data/test/dummy/config/database.yml +3 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/rabbit.yml +4 -0
- data/test/dummy/db/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/key_matcher_test.rb +100 -0
- data/test/log/.gitkeep +0 -0
- data/test/message_test.rb +80 -0
- data/test/rabbit_client_test.rb +71 -0
- data/test/rabbit_connection_test.rb +151 -0
- data/test/test_helper.rb +32 -0
- data/test/version_test.rb +8 -0
- data/tochtli.gemspec +129 -0
- metadata +259 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
class ControllerManager
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :rabbit_connection, :cache, :logger
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@controller_classes = Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(controller_class)
|
14
|
+
raise ArgumentError, "Controller expected, got: #{controller_class}" unless controller_class.is_a?(Class) && controller_class < Tochtli::BaseController
|
15
|
+
@controller_classes << controller_class
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup(options={})
|
19
|
+
@logger = options.fetch(:logger, Tochtli.logger)
|
20
|
+
@cache = options.fetch(:cache, Tochtli.cache)
|
21
|
+
@rabbit_connection = options[:connection]
|
22
|
+
|
23
|
+
unless @rabbit_connection
|
24
|
+
@rabbit_connection = RabbitConnection.open(options[:config])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def start(*controllers)
|
29
|
+
options = controllers.extract_options!
|
30
|
+
setup_options = options.except!(:logger, :cache, :connection)
|
31
|
+
queue_name = options.delete(:queue_name)
|
32
|
+
routing_keys = options.delete(:routing_keys)
|
33
|
+
initial_env = options.delete(:env) || {}
|
34
|
+
|
35
|
+
setup(setup_options) unless set_up?
|
36
|
+
|
37
|
+
if controllers.empty? || controllers.include?(:all)
|
38
|
+
controllers = @controller_classes
|
39
|
+
end
|
40
|
+
|
41
|
+
controllers.each do |controller_class|
|
42
|
+
raise ArgumentError, "Controller expected, got: #{controller_class.inspect}" unless controller_class.is_a?(Class) && controller_class < Tochtli::BaseController
|
43
|
+
unless controller_class.started?(queue_name)
|
44
|
+
@logger.info "Starting #{controller_class}..." if @logger
|
45
|
+
controller_class.setup(@rabbit_connection, @cache, @logger) unless controller_class.set_up?
|
46
|
+
controller_class.start queue_name, routing_keys, initial_env
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
@controller_classes.each do |controller_class|
|
53
|
+
if controller_class.started?
|
54
|
+
@logger.info "Stopping #{controller_class}..." if @logger
|
55
|
+
controller_class.stop
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@rabbit_connection = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def restart(options={})
|
62
|
+
options[:rabbit_connection] ||= @rabbit_connection
|
63
|
+
options[:logger] ||= @logger
|
64
|
+
options[:cache] ||= @cache
|
65
|
+
|
66
|
+
setup options
|
67
|
+
restart_active_controllers
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_up?
|
71
|
+
!@rabbit_connection.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
def running?
|
75
|
+
@rabbit_connection && @rabbit_connection.open?
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def restart_active_controllers
|
81
|
+
@controller_classes.each do |controller_class|
|
82
|
+
if controller_class.started?
|
83
|
+
@logger.info "Restarting #{controller_class}..." if @logger
|
84
|
+
controller_class.restart
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class << self
|
90
|
+
def method_missing(method, *args)
|
91
|
+
if instance.respond_to?(method)
|
92
|
+
instance.send(method, *args)
|
93
|
+
else
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Tochtli
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
initializer :eager_load_messages, :before => :bootstrap_hook do
|
5
|
+
Tochtli.eager_load_service_messages
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer :use_active_record_connection_release do
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
10
|
+
Tochtli.application.middlewares.use Tochtli::ActiveRecordConnectionCleaner
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
class Message
|
5
|
+
extend Uber::InheritableAttr
|
6
|
+
include Virtus.model
|
7
|
+
include Tochtli::SimpleValidation
|
8
|
+
|
9
|
+
inheritable_attr :routing_key
|
10
|
+
inheritable_attr :extra_attributes_policy
|
11
|
+
|
12
|
+
attr_reader :id, :properties
|
13
|
+
|
14
|
+
def self.route_to(routing_key=nil, &block)
|
15
|
+
self.routing_key = routing_key || block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compatibility with version 0.3
|
19
|
+
def self.attributes(*attributes)
|
20
|
+
options = attributes.extract_options!
|
21
|
+
required = options.fetch(:validate, true)
|
22
|
+
|
23
|
+
attributes.each do |name|
|
24
|
+
attribute name, String, required: required
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.required_attributes(*attributes)
|
29
|
+
options = attributes.extract_options!
|
30
|
+
self.attributes *attributes, options.merge(validate: true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.optional_attributes(*attributes)
|
34
|
+
options = attributes.extract_options!
|
35
|
+
self.attributes *attributes, options.merge(validate: false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.ignore_extra_attributes
|
39
|
+
self.extra_attributes_policy = :ignore
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(attributes={}, properties=nil)
|
43
|
+
super attributes || {}
|
44
|
+
|
45
|
+
@properties = properties
|
46
|
+
@id = properties.message_id if properties
|
47
|
+
@id ||= self.class.generate_id
|
48
|
+
|
49
|
+
store_extra_attributes(attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
def attributes=(attributes)
|
53
|
+
super
|
54
|
+
store_extra_attributes(attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
def store_extra_attributes(attributes)
|
58
|
+
@extra_attributes ||= {}
|
59
|
+
if attributes
|
60
|
+
attributes.each do |name, value|
|
61
|
+
unless allowed_writer_methods.include?("#{name}=")
|
62
|
+
@extra_attributes[name] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_extra_attributes
|
69
|
+
if self.class.extra_attributes_policy != :ignore && !@extra_attributes.empty?
|
70
|
+
add_error "Unexpected attributes: #{@extra_attributes.keys.map(&:to_s).join(', ')}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_attributes_presence
|
75
|
+
nil_attributes = attribute_set.select { |a| a.required? && self[a.name].nil? }.map(&:name)
|
76
|
+
unless nil_attributes.empty?
|
77
|
+
add_error "Required attributes: #{nil_attributes.map(&:to_s).join(', ')} not specified"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate
|
82
|
+
validate_extra_attributes
|
83
|
+
validate_attributes_presence
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.generate_id
|
87
|
+
SecureRandom.uuid
|
88
|
+
end
|
89
|
+
|
90
|
+
def routing_key
|
91
|
+
if self.class.routing_key.is_a?(Proc)
|
92
|
+
self.instance_eval(&self.class.routing_key)
|
93
|
+
else
|
94
|
+
self.class.routing_key
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_hash
|
99
|
+
attributes.inject({}) do |hash, (name, value)|
|
100
|
+
value = value.map(&:to_hash) if value.is_a?(Array)
|
101
|
+
hash[name] = value
|
102
|
+
hash
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_json
|
107
|
+
JSON.dump(to_hash)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class ErrorMessage < Message
|
112
|
+
attributes :error, :message
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Tochtli
|
2
|
+
class RabbitClient
|
3
|
+
|
4
|
+
attr_reader :rabbit_connection
|
5
|
+
|
6
|
+
def initialize(rabbit_connection=nil, logger=nil)
|
7
|
+
if rabbit_connection
|
8
|
+
@rabbit_connection = rabbit_connection
|
9
|
+
else
|
10
|
+
@rabbit_connection = Tochtli::RabbitConnection.open(nil, logger: logger)
|
11
|
+
end
|
12
|
+
@logger = logger || @rabbit_connection.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def publish(message, options={})
|
16
|
+
raise InvalidMessageError.new(message.errors.join(", "), message) if message.invalid?
|
17
|
+
|
18
|
+
@logger.debug "[#{Time.now} AMQP] Publishing message #{message.id} to #{message.routing_key}"
|
19
|
+
|
20
|
+
reply_queue = @rabbit_connection.reply_queue
|
21
|
+
options[:reply_to] = reply_queue.name
|
22
|
+
if (message_handler = options[:handler])
|
23
|
+
reply_queue.register_message_handler message, message_handler, options[:timeout]
|
24
|
+
end
|
25
|
+
@rabbit_connection.publish message.routing_key, message, options
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_for_confirms
|
29
|
+
@rabbit_connection.channel.wait_for_confirms
|
30
|
+
end
|
31
|
+
|
32
|
+
def reply_queue(*args)
|
33
|
+
rabbit_connection.reply_queue(*args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Tochtli
|
5
|
+
class RabbitConnection
|
6
|
+
attr_accessor :connection
|
7
|
+
attr_reader :logger, :exchange_name
|
8
|
+
|
9
|
+
cattr_accessor :connections
|
10
|
+
self.connections = {}
|
11
|
+
|
12
|
+
private_class_method :new
|
13
|
+
|
14
|
+
DEFAULT_CONNECTION_NAME = 'default'
|
15
|
+
|
16
|
+
def initialize(config = nil, channel_pool=nil)
|
17
|
+
@config = config.is_a?(RabbitConnection::Config) ? config : RabbitConnection::Config.load(nil, config)
|
18
|
+
@exchange_name = @config.delete(:exchange_name)
|
19
|
+
@work_pool_size = @config.delete(:work_pool_size)
|
20
|
+
@logger = @config.delete(:logger) || Tochtli.logger
|
21
|
+
@channel_pool = channel_pool ? channel_pool : Hash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.open(name=nil, config=nil)
|
25
|
+
name ||= defined?(Rails) ? Rails.env : DEFAULT_CONNECTION_NAME
|
26
|
+
raise ArgumentError, "RabbitMQ configuration name not specified" if !name && !ENV.has_key?('RABBITMQ_URL')
|
27
|
+
connection = self.connections[name.to_sym]
|
28
|
+
if !connection || !connection.open?
|
29
|
+
config = config.is_a?(RabbitConnection::Config) ? config : RabbitConnection::Config.load(name, config)
|
30
|
+
connection = new(config)
|
31
|
+
connection.connect
|
32
|
+
self.connections[name.to_sym] = connection
|
33
|
+
end
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
yield connection
|
37
|
+
close name
|
38
|
+
else
|
39
|
+
connection
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.close(name=nil)
|
44
|
+
name ||= defined?(Rails) ? Rails.env : nil
|
45
|
+
raise ArgumentError, "RabbitMQ configuration name not specified" unless name
|
46
|
+
connection = self.connections.delete(name.to_sym)
|
47
|
+
connection.disconnect if connection && connection.open?
|
48
|
+
end
|
49
|
+
|
50
|
+
def connect(opts={})
|
51
|
+
return if open?
|
52
|
+
|
53
|
+
defaults = {}
|
54
|
+
unless opts[:logger]
|
55
|
+
defaults[:logger] = @logger.dup
|
56
|
+
defaults[:logger].level = Tochtli.debug_bunny ? Logger::DEBUG : Logger::WARN
|
57
|
+
end
|
58
|
+
|
59
|
+
setup_bunny_connection(defaults.merge(opts))
|
60
|
+
|
61
|
+
if block_given?
|
62
|
+
yield
|
63
|
+
disconnect if open?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def disconnect
|
68
|
+
@connection.close if @connection
|
69
|
+
rescue Bunny::ClientTimeout
|
70
|
+
false
|
71
|
+
ensure
|
72
|
+
@channel_pool.clear
|
73
|
+
@connection = nil
|
74
|
+
@reply_queue = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def open?
|
78
|
+
@connection && @connection.open?
|
79
|
+
end
|
80
|
+
|
81
|
+
def setup_bunny_connection(opts={})
|
82
|
+
@connection = Bunny.new(@config, opts)
|
83
|
+
@connection.start
|
84
|
+
rescue Bunny::TCPConnectionFailed => ex
|
85
|
+
connection_url = "amqp://#{@connection.user}@#{@connection.host}:#{@connection.port}/#{@connection.vhost}"
|
86
|
+
raise ConnectionFailed.new("Unable to connect to: '#{connection_url}' (#{ex.message})")
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_reply_queue
|
90
|
+
Tochtli::ReplyQueue.new(self, @logger)
|
91
|
+
end
|
92
|
+
|
93
|
+
def reply_queue
|
94
|
+
@reply_queue ||= create_reply_queue
|
95
|
+
end
|
96
|
+
|
97
|
+
def exchange(thread=Thread.current)
|
98
|
+
channel_wrap(thread).exchange
|
99
|
+
end
|
100
|
+
|
101
|
+
def channel(thread=Thread.current)
|
102
|
+
channel_wrap(thread).channel
|
103
|
+
end
|
104
|
+
|
105
|
+
def queue(name, routing_keys=[], options={})
|
106
|
+
queue = channel.queue(name, {durable: true}.merge(options))
|
107
|
+
routing_keys.each do |routing_key|
|
108
|
+
queue.bind(exchange, routing_key: routing_key)
|
109
|
+
end
|
110
|
+
queue
|
111
|
+
end
|
112
|
+
|
113
|
+
def queue_exists?(name)
|
114
|
+
@connection.queue_exists?(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
def ack(delivery_tag)
|
118
|
+
channel.ack(delivery_tag, false)
|
119
|
+
end
|
120
|
+
|
121
|
+
def publish(routing_key, message, options={})
|
122
|
+
begin
|
123
|
+
payload = message.to_json
|
124
|
+
rescue Exception
|
125
|
+
logger.error "Unable to serialize message: #{message.inspect}"
|
126
|
+
logger.error $!
|
127
|
+
raise "Unable to serialize message to JSON: #{$!}"
|
128
|
+
end
|
129
|
+
|
130
|
+
exchange.publish(payload, {
|
131
|
+
routing_key: routing_key,
|
132
|
+
persistent: true,
|
133
|
+
mandatory: true,
|
134
|
+
timestamp: Time.now.to_i,
|
135
|
+
message_id: message.id,
|
136
|
+
type: message.class.name.underscore,
|
137
|
+
content_type: "application/json"
|
138
|
+
}.merge(options))
|
139
|
+
end
|
140
|
+
|
141
|
+
def create_channel(consumer_pool_size = 1)
|
142
|
+
@connection.create_channel(nil, consumer_pool_size).tap do |channel|
|
143
|
+
channel.confirm_select # use publisher confirmations
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_exchange(channel)
|
148
|
+
channel.topic(@exchange_name, durable: true)
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def on_return(return_info, properties, payload)
|
154
|
+
unless properties[:correlation_id]
|
155
|
+
error_message = "Message #{properties[:message_id]} dropped: #{return_info[:reply_text]} [#{return_info[:reply_code]}]"
|
156
|
+
reply_queue.handle_reply MessageDropped.new(error_message, payload), properties[:message_id]
|
157
|
+
else # a reply dropped - client reply queue probably does not exist any more
|
158
|
+
logger.debug "Reply on message #{properties[:correlation_id]} dropped: #{return_info[:reply_text]} [#{return_info[:reply_code]}]"
|
159
|
+
end
|
160
|
+
rescue
|
161
|
+
logger.error "Internal error (on_return): #{$!}"
|
162
|
+
logger.error $!.backtrace.join("\n")
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_channel_wrap(thread=Thread.current)
|
166
|
+
raise ConnectionFailed.new("Channel already created for thread #{thread.object_id}") if @channel_pool[thread.object_id]
|
167
|
+
raise ConnectionFailed.new("Unable to create channel. Connection lost.") unless @connection
|
168
|
+
|
169
|
+
channel = create_channel(@work_pool_size)
|
170
|
+
exchange = create_exchange(channel)
|
171
|
+
exchange.on_return &method(:on_return)
|
172
|
+
|
173
|
+
channel_wrap = ChannelWrap.new(channel, exchange)
|
174
|
+
@channel_pool[thread.object_id] = channel_wrap
|
175
|
+
|
176
|
+
channel_wrap
|
177
|
+
rescue Bunny::PreconditionFailed => ex
|
178
|
+
raise ConnectionFailed.new("Unable create exchange: '#{@exchange_name}': #{ex.message}")
|
179
|
+
end
|
180
|
+
|
181
|
+
def channel_wrap(thread=Thread.current)
|
182
|
+
channel_wrap = @channel_pool[thread.object_id]
|
183
|
+
if channel_wrap && channel_wrap.channel.active
|
184
|
+
channel_wrap
|
185
|
+
else
|
186
|
+
@channel_pool.delete(thread.object_id) # ensure inactive channel s not cached
|
187
|
+
create_channel_wrap(thread)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def generate_id
|
192
|
+
SecureRandom.uuid
|
193
|
+
end
|
194
|
+
|
195
|
+
class ChannelWrap
|
196
|
+
attr_reader :channel, :exchange
|
197
|
+
|
198
|
+
def initialize(channel, exchange)
|
199
|
+
@channel = channel
|
200
|
+
@exchange = exchange
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Config < Hash
|
205
|
+
DEFAULTS = {
|
206
|
+
:exchange_name => "puzzleflow.services",
|
207
|
+
:work_pool_size => 1,
|
208
|
+
:automatically_recover => true,
|
209
|
+
:network_recovery_interval => 1
|
210
|
+
}
|
211
|
+
|
212
|
+
def self.load(name, config=nil)
|
213
|
+
config = case config
|
214
|
+
when String
|
215
|
+
YAML.load_file(config).symbolize_keys
|
216
|
+
when Hash
|
217
|
+
config.symbolize_keys
|
218
|
+
when nil
|
219
|
+
{}
|
220
|
+
else
|
221
|
+
raise "Unexpected configuration: #{config.inspect}, Hash or String expected."
|
222
|
+
end
|
223
|
+
|
224
|
+
defaults = DEFAULTS
|
225
|
+
|
226
|
+
if defined?(Rails) && Rails.root
|
227
|
+
config_path = Rails.root.join('config/rabbit.yml')
|
228
|
+
if config_path.exist?
|
229
|
+
rails_config = YAML.load_file(config_path)
|
230
|
+
raise "Unexpected rabbit.yml: #{rails_config.inspect}, Hash expected." unless rails_config.is_a?(Hash)
|
231
|
+
rails_config = rails_config.symbolize_keys
|
232
|
+
unless rails_config[:host] # backward compatibility
|
233
|
+
rails_config = rails_config[name.to_sym]
|
234
|
+
raise "RabbitMQ '#{name}' configuration not set in rabbit.yml" unless rails_config
|
235
|
+
else
|
236
|
+
warn "DEPRECATION WARNING: rabbit.yml should define different configurations for Rails environments (like database.yml). Please update your configuration file: #{config_path}."
|
237
|
+
end
|
238
|
+
defaults = defaults.merge(rails_config.symbolize_keys)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
new.merge!(defaults.merge(config))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class ConnectionFailed < StandardError
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|