hutch 0.18.0 → 1.1.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 +5 -5
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +20 -8
- data/.yardopts +5 -0
- data/CHANGELOG.md +466 -2
- data/Gemfile +18 -4
- data/Guardfile +13 -4
- data/LICENSE +2 -1
- data/README.md +397 -32
- data/Rakefile +8 -1
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/hutch.gemspec +6 -7
- data/lib/hutch/acknowledgements/base.rb +16 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
- data/lib/hutch/adapters/march_hare.rb +1 -1
- data/lib/hutch/broker.rb +127 -103
- data/lib/hutch/cli.rb +66 -25
- data/lib/hutch/config.rb +230 -55
- data/lib/hutch/consumer.rb +42 -3
- data/lib/hutch/error_handlers/airbrake.rb +44 -16
- data/lib/hutch/error_handlers/base.rb +15 -0
- data/lib/hutch/error_handlers/bugsnag.rb +30 -0
- data/lib/hutch/error_handlers/honeybadger.rb +33 -18
- data/lib/hutch/error_handlers/logger.rb +12 -6
- data/lib/hutch/error_handlers/rollbar.rb +28 -0
- data/lib/hutch/error_handlers/sentry.rb +15 -12
- data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
- data/lib/hutch/error_handlers.rb +3 -0
- data/lib/hutch/exceptions.rb +8 -1
- data/lib/hutch/logging.rb +5 -5
- data/lib/hutch/message.rb +2 -4
- data/lib/hutch/publisher.rb +75 -0
- data/lib/hutch/serializers/identity.rb +19 -0
- data/lib/hutch/serializers/json.rb +22 -0
- data/lib/hutch/tracers/datadog.rb +17 -0
- data/lib/hutch/tracers.rb +1 -0
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/waiter.rb +104 -0
- data/lib/hutch/worker.rb +81 -75
- data/lib/hutch.rb +15 -6
- data/lib/yard-settings/handler.rb +38 -0
- data/lib/yard-settings/yard-settings.rb +2 -0
- data/spec/hutch/broker_spec.rb +162 -77
- data/spec/hutch/cli_spec.rb +16 -3
- data/spec/hutch/config_spec.rb +121 -22
- data/spec/hutch/consumer_spec.rb +82 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
- data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
- data/spec/hutch/error_handlers/logger_spec.rb +14 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +21 -2
- data/spec/hutch/logger_spec.rb +12 -6
- data/spec/hutch/message_spec.rb +2 -2
- data/spec/hutch/serializers/json_spec.rb +17 -0
- data/spec/hutch/tracers/datadog_spec.rb +44 -0
- data/spec/hutch/waiter_spec.rb +51 -0
- data/spec/hutch/worker_spec.rb +89 -5
- data/spec/spec_helper.rb +7 -5
- data/templates/default/class/html/settings.erb +0 -0
- data/templates/default/class/setup.rb +4 -0
- data/templates/default/fulldoc/html/css/hutch.css +13 -0
- data/templates/default/layout/html/setup.rb +7 -0
- data/templates/default/method_details/html/settings.erb +5 -0
- data/templates/default/method_details/setup.rb +4 -0
- data/templates/default/method_details/text/settings.erb +0 -0
- data/templates/default/module/html/settings.erb +40 -0
- data/templates/default/module/setup.rb +4 -0
- metadata +62 -43
- data/circle.yml +0 -3
data/lib/hutch/broker.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
1
3
|
require 'carrot-top'
|
2
|
-
require 'securerandom'
|
3
4
|
require 'hutch/logging'
|
4
5
|
require 'hutch/exceptions'
|
6
|
+
require 'hutch/publisher'
|
5
7
|
|
6
8
|
module Hutch
|
7
9
|
class Broker
|
@@ -9,10 +11,38 @@ module Hutch
|
|
9
11
|
|
10
12
|
attr_accessor :connection, :channel, :exchange, :api_client
|
11
13
|
|
14
|
+
|
15
|
+
DEFAULT_AMQP_PORT =
|
16
|
+
case RUBY_ENGINE
|
17
|
+
when "jruby" then
|
18
|
+
com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_PORT
|
19
|
+
when "ruby" then
|
20
|
+
AMQ::Protocol::DEFAULT_PORT
|
21
|
+
end
|
22
|
+
|
23
|
+
DEFAULT_AMQPS_PORT =
|
24
|
+
case RUBY_ENGINE
|
25
|
+
when "jruby" then
|
26
|
+
com.rabbitmq.client.ConnectionFactory::DEFAULT_AMQP_OVER_SSL_PORT
|
27
|
+
when "ruby" then
|
28
|
+
AMQ::Protocol::TLS_PORT
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# @param config [nil,Hash] Configuration override
|
12
33
|
def initialize(config = nil)
|
13
34
|
@config = config || Hutch::Config
|
14
35
|
end
|
15
36
|
|
37
|
+
# Connect to broker
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# Hutch::Broker.new.connect(enable_http_api_use: true) do
|
41
|
+
# # will disconnect after this block
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param [Hash] options The options to connect with
|
45
|
+
# @option options [Boolean] :enable_http_api_use
|
16
46
|
def connect(options = {})
|
17
47
|
@options = options
|
18
48
|
set_up_amqp_connection
|
@@ -41,41 +71,44 @@ module Hutch
|
|
41
71
|
def disconnect
|
42
72
|
@channel.close if @channel
|
43
73
|
@connection.close if @connection
|
44
|
-
@channel
|
74
|
+
@channel = nil
|
75
|
+
@connection = nil
|
76
|
+
@exchange = nil
|
77
|
+
@api_client = nil
|
45
78
|
end
|
46
79
|
|
47
|
-
# Connect to RabbitMQ via AMQP
|
48
|
-
#
|
49
|
-
# the
|
80
|
+
# Connect to RabbitMQ via AMQP
|
81
|
+
#
|
82
|
+
# This sets up the main connection and channel we use for talking to
|
83
|
+
# RabbitMQ. It also ensures the existence of the exchange we'll be using.
|
50
84
|
def set_up_amqp_connection
|
51
85
|
open_connection!
|
52
86
|
open_channel!
|
53
|
-
|
54
|
-
|
55
|
-
logger.info "using topic exchange '#{exchange_name}'"
|
56
|
-
|
57
|
-
with_bunny_precondition_handler('exchange') do
|
58
|
-
@exchange = @channel.topic(exchange_name, durable: true)
|
59
|
-
end
|
87
|
+
declare_exchange!
|
88
|
+
declare_publisher!
|
60
89
|
end
|
61
90
|
|
62
|
-
def open_connection
|
91
|
+
def open_connection
|
63
92
|
logger.info "connecting to rabbitmq (#{sanitized_uri})"
|
64
93
|
|
65
|
-
|
94
|
+
connection = Hutch::Adapter.new(connection_params)
|
66
95
|
|
67
96
|
with_bunny_connection_handler(sanitized_uri) do
|
68
|
-
|
97
|
+
connection.start
|
69
98
|
end
|
70
99
|
|
71
100
|
logger.info "connected to RabbitMQ at #{connection_params[:host]} as #{connection_params[:username]}"
|
72
|
-
|
101
|
+
connection
|
73
102
|
end
|
74
103
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
104
|
+
def open_connection!
|
105
|
+
@connection = open_connection
|
106
|
+
end
|
107
|
+
|
108
|
+
def open_channel
|
109
|
+
logger.info "opening rabbitmq channel with pool size #{consumer_pool_size}, abort on exception #{consumer_pool_abort_on_exception}"
|
110
|
+
connection.create_channel(nil, consumer_pool_size, consumer_pool_abort_on_exception).tap do |ch|
|
111
|
+
connection.prefetch_channel(ch, @config[:channel_prefetch])
|
79
112
|
if @config[:publisher_confirms] || @config[:force_publisher_confirms]
|
80
113
|
logger.info 'enabling publisher confirms'
|
81
114
|
ch.confirm_select
|
@@ -83,6 +116,29 @@ module Hutch
|
|
83
116
|
end
|
84
117
|
end
|
85
118
|
|
119
|
+
def open_channel!
|
120
|
+
@channel = open_channel
|
121
|
+
end
|
122
|
+
|
123
|
+
def declare_exchange(ch = channel)
|
124
|
+
exchange_name = @config[:mq_exchange]
|
125
|
+
exchange_type = @config[:mq_exchange_type]
|
126
|
+
exchange_options = { durable: true }.merge(@config[:mq_exchange_options])
|
127
|
+
logger.info "using topic exchange '#{exchange_name}'"
|
128
|
+
|
129
|
+
with_bunny_precondition_handler('exchange') do
|
130
|
+
Bunny::Exchange.new(ch, exchange_type, exchange_name, exchange_options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def declare_exchange!(*args)
|
135
|
+
@exchange = declare_exchange(*args)
|
136
|
+
end
|
137
|
+
|
138
|
+
def declare_publisher!
|
139
|
+
@publisher = Hutch::Publisher.new(connection, channel, exchange, @config)
|
140
|
+
end
|
141
|
+
|
86
142
|
# Set up the connection to the RabbitMQ management API. Unfortunately, this
|
87
143
|
# is necessary to do a few things that are impossible over AMQP. E.g.
|
88
144
|
# listing queues and bindings.
|
@@ -118,7 +174,7 @@ module Hutch
|
|
118
174
|
def queue(name, arguments = {})
|
119
175
|
with_bunny_precondition_handler('queue') do
|
120
176
|
namespace = @config[:namespace].to_s.downcase.gsub(/[^-_:\.\w]/, "")
|
121
|
-
name = name.prepend(namespace + ":")
|
177
|
+
name = name.prepend(namespace + ":") if namespace.present?
|
122
178
|
channel.queue(name, durable: true, arguments: arguments)
|
123
179
|
end
|
124
180
|
end
|
@@ -126,46 +182,44 @@ module Hutch
|
|
126
182
|
# Return a mapping of queue names to the routing keys they're bound to.
|
127
183
|
def bindings
|
128
184
|
results = Hash.new { |hash, key| hash[key] = [] }
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
185
|
+
|
186
|
+
filtered = api_client.bindings.
|
187
|
+
reject { |b| b['destination'] == b['routing_key'] }.
|
188
|
+
select { |b| b['source'] == @config[:mq_exchange] && b['vhost'] == @config[:mq_vhost] }
|
189
|
+
|
190
|
+
filtered.each do |binding|
|
133
191
|
results[binding['destination']] << binding['routing_key']
|
134
192
|
end
|
193
|
+
|
135
194
|
results
|
136
195
|
end
|
137
196
|
|
197
|
+
# Find the existing bindings, and unbind any redundant bindings
|
198
|
+
def unbind_redundant_bindings(queue, routing_keys)
|
199
|
+
return unless http_api_use_enabled?
|
200
|
+
|
201
|
+
filtered = bindings.select { |dest, keys| dest == queue.name }
|
202
|
+
filtered.each do |dest, keys|
|
203
|
+
keys.reject { |key| routing_keys.include?(key) }.each do |key|
|
204
|
+
logger.debug "removing redundant binding #{queue.name} <--> #{key}"
|
205
|
+
queue.unbind(exchange, routing_key: key)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
138
210
|
# Bind a queue to the broker's exchange on the routing keys provided. Any
|
139
211
|
# existing bindings on the queue that aren't present in the array of
|
140
212
|
# routing keys will be unbound.
|
141
213
|
def bind_queue(queue, routing_keys)
|
142
|
-
|
143
|
-
# Find the existing bindings, and unbind any redundant bindings
|
144
|
-
queue_bindings = bindings.select { |dest, keys| dest == queue.name }
|
145
|
-
queue_bindings.each do |dest, keys|
|
146
|
-
keys.reject { |key| routing_keys.include?(key) }.each do |key|
|
147
|
-
logger.debug "removing redundant binding #{queue.name} <--> #{key}"
|
148
|
-
queue.unbind(@exchange, routing_key: key)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
214
|
+
unbind_redundant_bindings(queue, routing_keys)
|
152
215
|
|
153
216
|
# Ensure all the desired bindings are present
|
154
217
|
routing_keys.each do |routing_key|
|
155
218
|
logger.debug "creating binding #{queue.name} <--> #{routing_key}"
|
156
|
-
queue.bind(
|
219
|
+
queue.bind(exchange, routing_key: routing_key)
|
157
220
|
end
|
158
221
|
end
|
159
222
|
|
160
|
-
# Each subscriber is run in a thread. This calls Thread#join on each of the
|
161
|
-
# subscriber threads.
|
162
|
-
def wait_on_threads(timeout)
|
163
|
-
# Thread#join returns nil when the timeout is hit. If any return nil,
|
164
|
-
# the threads didn't all join so we return false.
|
165
|
-
per_thread_timeout = timeout.to_f / work_pool_threads.length
|
166
|
-
work_pool_threads.none? { |thread| thread.join(per_thread_timeout).nil? }
|
167
|
-
end
|
168
|
-
|
169
223
|
def stop
|
170
224
|
if defined?(JRUBY_VERSION)
|
171
225
|
channel.close
|
@@ -180,67 +234,40 @@ module Hutch
|
|
180
234
|
end
|
181
235
|
|
182
236
|
def requeue(delivery_tag)
|
183
|
-
|
237
|
+
channel.reject(delivery_tag, true)
|
184
238
|
end
|
185
239
|
|
186
240
|
def reject(delivery_tag, requeue=false)
|
187
|
-
|
241
|
+
channel.reject(delivery_tag, requeue)
|
188
242
|
end
|
189
243
|
|
190
244
|
def ack(delivery_tag)
|
191
|
-
|
245
|
+
channel.ack(delivery_tag, false)
|
192
246
|
end
|
193
247
|
|
194
248
|
def nack(delivery_tag)
|
195
|
-
|
249
|
+
channel.nack(delivery_tag, false, false)
|
196
250
|
end
|
197
251
|
|
198
|
-
def publish(
|
199
|
-
|
200
|
-
|
201
|
-
non_overridable_properties = {
|
202
|
-
routing_key: routing_key,
|
203
|
-
timestamp: @connection.current_timestamp,
|
204
|
-
content_type: 'application/json'
|
205
|
-
}
|
206
|
-
properties[:message_id] ||= generate_id
|
207
|
-
|
208
|
-
json = JSON.dump(message)
|
209
|
-
logger.info("publishing message '#{json}' to #{routing_key}")
|
210
|
-
response = @exchange.publish(json, {persistent: true}.
|
211
|
-
merge(properties).
|
212
|
-
merge(global_properties).
|
213
|
-
merge(non_overridable_properties))
|
214
|
-
|
215
|
-
channel.wait_for_confirms if @config[:force_publisher_confirms]
|
216
|
-
response
|
252
|
+
def publish(*args)
|
253
|
+
@publisher.publish(*args)
|
217
254
|
end
|
218
255
|
|
219
256
|
def confirm_select(*args)
|
220
|
-
|
257
|
+
channel.confirm_select(*args)
|
221
258
|
end
|
222
259
|
|
223
260
|
def wait_for_confirms
|
224
|
-
|
261
|
+
channel.wait_for_confirms
|
225
262
|
end
|
226
263
|
|
264
|
+
# @return [Boolean] True if channel is set up to use publisher confirmations.
|
227
265
|
def using_publisher_confirmations?
|
228
|
-
|
266
|
+
channel.using_publisher_confirmations?
|
229
267
|
end
|
230
268
|
|
231
269
|
private
|
232
270
|
|
233
|
-
def raise_publish_error(reason, routing_key, message)
|
234
|
-
msg = "unable to publish - #{reason}. Message: #{JSON.dump(message)}, Routing key: #{routing_key}."
|
235
|
-
logger.error(msg)
|
236
|
-
raise PublishError, msg
|
237
|
-
end
|
238
|
-
|
239
|
-
def ensure_connection!(routing_key, message)
|
240
|
-
raise_publish_error('no connection to broker', routing_key, message) unless @connection
|
241
|
-
raise_publish_error('connection is closed', routing_key, message) unless @connection.open?
|
242
|
-
end
|
243
|
-
|
244
271
|
def api_config
|
245
272
|
@api_config ||= OpenStruct.new.tap do |config|
|
246
273
|
config.host = @config[:mq_api_host]
|
@@ -259,41 +286,46 @@ module Hutch
|
|
259
286
|
{}.tap do |params|
|
260
287
|
params[:host] = @config[:mq_host]
|
261
288
|
params[:port] = @config[:mq_port]
|
262
|
-
params[:vhost] =
|
263
|
-
@config[:mq_vhost]
|
264
|
-
else
|
265
|
-
Hutch::Adapter::DEFAULT_VHOST
|
266
|
-
end
|
289
|
+
params[:vhost] = @config[:mq_vhost].presence || Hutch::Adapter::DEFAULT_VHOST
|
267
290
|
params[:username] = @config[:mq_username]
|
268
291
|
params[:password] = @config[:mq_password]
|
269
292
|
params[:tls] = @config[:mq_tls]
|
270
293
|
params[:tls_key] = @config[:mq_tls_key]
|
271
294
|
params[:tls_cert] = @config[:mq_tls_cert]
|
295
|
+
params[:verify_peer] = @config[:mq_verify_peer]
|
296
|
+
if @config[:mq_tls_ca_certificates]
|
297
|
+
params[:tls_ca_certificates] = @config[:mq_tls_ca_certificates]
|
298
|
+
end
|
272
299
|
params[:heartbeat] = @config[:heartbeat]
|
273
300
|
params[:connection_timeout] = @config[:connection_timeout]
|
274
301
|
params[:read_timeout] = @config[:read_timeout]
|
275
302
|
params[:write_timeout] = @config[:write_timeout]
|
276
303
|
|
277
304
|
|
278
|
-
params[:automatically_recover] =
|
279
|
-
params[:network_recovery_interval] =
|
305
|
+
params[:automatically_recover] = @config[:automatically_recover]
|
306
|
+
params[:network_recovery_interval] = @config[:network_recovery_interval]
|
280
307
|
|
281
|
-
params[:
|
308
|
+
params[:logger] = @config[:client_logger] if @config[:client_logger]
|
282
309
|
end
|
283
310
|
end
|
284
311
|
|
285
312
|
def parse_uri
|
286
|
-
return
|
313
|
+
return if @config[:uri].blank?
|
287
314
|
|
288
315
|
u = URI.parse(@config[:uri])
|
289
316
|
|
317
|
+
@config[:mq_tls] = u.scheme == 'amqps'
|
290
318
|
@config[:mq_host] = u.host
|
291
|
-
@config[:mq_port] = u.port
|
319
|
+
@config[:mq_port] = u.port || default_mq_port
|
292
320
|
@config[:mq_vhost] = u.path.sub(/^\//, "")
|
293
321
|
@config[:mq_username] = u.user
|
294
322
|
@config[:mq_password] = u.password
|
295
323
|
end
|
296
324
|
|
325
|
+
def default_mq_port
|
326
|
+
@config[:mq_tls] ? DEFAULT_AMQPS_PORT : DEFAULT_AMQP_PORT
|
327
|
+
end
|
328
|
+
|
297
329
|
def sanitized_uri
|
298
330
|
p = connection_params
|
299
331
|
scheme = p[:tls] ? "amqps" : "amqp"
|
@@ -335,24 +367,16 @@ module Hutch
|
|
335
367
|
raise ConnectionError.new("couldn't connect to rabbitmq at #{uri}. Check your configuration, network connectivity and RabbitMQ logs.")
|
336
368
|
end
|
337
369
|
|
338
|
-
def work_pool_threads
|
339
|
-
channel_work_pool.threads || []
|
340
|
-
end
|
341
|
-
|
342
370
|
def channel_work_pool
|
343
|
-
|
371
|
+
channel.work_pool
|
344
372
|
end
|
345
373
|
|
346
374
|
def consumer_pool_size
|
347
375
|
@config[:consumer_pool_size]
|
348
376
|
end
|
349
377
|
|
350
|
-
def
|
351
|
-
|
352
|
-
end
|
353
|
-
|
354
|
-
def global_properties
|
355
|
-
Hutch.global_properties.respond_to?(:call) ? Hutch.global_properties.call : Hutch.global_properties
|
378
|
+
def consumer_pool_abort_on_exception
|
379
|
+
@config[:consumer_pool_abort_on_exception]
|
356
380
|
end
|
357
381
|
end
|
358
382
|
end
|
data/lib/hutch/cli.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
|
3
|
-
require 'hutch/version'
|
4
3
|
require 'hutch/logging'
|
5
|
-
require 'hutch/exceptions'
|
6
4
|
require 'hutch/config'
|
5
|
+
require 'hutch/version'
|
6
|
+
require 'hutch/exceptions'
|
7
7
|
|
8
8
|
module Hutch
|
9
9
|
class CLI
|
@@ -11,9 +11,10 @@ module Hutch
|
|
11
11
|
|
12
12
|
# Run a Hutch worker with the command line interface.
|
13
13
|
def run(argv = ARGV)
|
14
|
+
Hutch::Config.initialize
|
14
15
|
parse_options(argv)
|
15
16
|
|
16
|
-
|
17
|
+
daemonise_process
|
17
18
|
|
18
19
|
write_pid if Hutch::Config.pidfile
|
19
20
|
|
@@ -32,6 +33,23 @@ module Hutch
|
|
32
33
|
def load_app
|
33
34
|
# Try to load a Rails app in the current directory
|
34
35
|
load_rails_app('.') if Hutch::Config.autoload_rails
|
36
|
+
set_up_code_paths!
|
37
|
+
|
38
|
+
# Because of the order things are required when we run the Hutch binary
|
39
|
+
# in hutch/bin, the Sentry Raven gem gets required **after** the error
|
40
|
+
# handlers are set up. Due to this, we never got any Sentry notifications
|
41
|
+
# when an error occurred in any of the consumers.
|
42
|
+
if defined?(Raven)
|
43
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::SentryRaven.new
|
44
|
+
end
|
45
|
+
if defined?(Sentry)
|
46
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
47
|
+
end
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_up_code_paths!
|
35
53
|
Hutch::Config.require_paths.each do |path|
|
36
54
|
# See if each path is a Rails app. If so, try to load it.
|
37
55
|
next if load_rails_app(path)
|
@@ -42,34 +60,27 @@ module Hutch
|
|
42
60
|
# Need to add '.' to load path for relative requires
|
43
61
|
$LOAD_PATH << '.'
|
44
62
|
require path
|
45
|
-
rescue LoadError
|
46
|
-
logger.fatal "could not load file '#{path}'"
|
63
|
+
rescue LoadError => e
|
64
|
+
logger.fatal "could not load file '#{path}': #{e}"
|
47
65
|
return false
|
48
66
|
ensure
|
49
67
|
# Clean up load path
|
50
68
|
$LOAD_PATH.pop
|
51
69
|
end
|
52
70
|
end
|
53
|
-
|
54
|
-
# Because of the order things are required when we run the Hutch binary
|
55
|
-
# in hutch/bin, the Sentry Raven gem gets required **after** the error
|
56
|
-
# handlers are set up. Due to this, we never got any Sentry notifications
|
57
|
-
# when an error occurred in any of the consumers.
|
58
|
-
if defined?(Raven)
|
59
|
-
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
60
|
-
end
|
61
|
-
|
62
|
-
true
|
63
71
|
end
|
64
72
|
|
65
73
|
def load_rails_app(path)
|
66
74
|
# path should point to the app's top level directory
|
67
75
|
if File.directory?(path)
|
68
|
-
# Smells like a Rails app if it's got a
|
76
|
+
# Smells like a Rails app if it's got a script/rails or bin/rails file
|
77
|
+
is_rails_app = ['script/rails', 'bin/rails'].any? do |file|
|
78
|
+
File.exist?(File.expand_path(File.join(path, file)))
|
79
|
+
end
|
69
80
|
rails_path = File.expand_path(File.join(path, 'config/environment.rb'))
|
70
|
-
if File.
|
71
|
-
logger.info "found rails project (#{path}), booting app"
|
81
|
+
if is_rails_app && File.exist?(rails_path)
|
72
82
|
ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] || 'development'
|
83
|
+
logger.info "found rails project (#{path}), booting app in #{ENV['RACK_ENV']} environment"
|
73
84
|
require rails_path
|
74
85
|
::Rails.application.eager_load!
|
75
86
|
return true
|
@@ -82,10 +93,13 @@ module Hutch
|
|
82
93
|
# gracefully (with a SIGQUIT, SIGTERM or SIGINT).
|
83
94
|
def start_work_loop
|
84
95
|
Hutch.connect
|
85
|
-
@worker = Hutch::Worker.new(Hutch.broker, Hutch.consumers)
|
96
|
+
@worker = Hutch::Worker.new(Hutch.broker, Hutch.consumers, Hutch::Config.setup_procs)
|
86
97
|
@worker.run
|
87
98
|
:success
|
88
99
|
rescue ConnectionError, AuthenticationError, WorkerSetupError => ex
|
100
|
+
Hutch::Config[:error_handlers].each do |backend|
|
101
|
+
backend.handle_setup_exception(ex)
|
102
|
+
end
|
89
103
|
logger.fatal ex.message
|
90
104
|
:error
|
91
105
|
end
|
@@ -106,14 +120,16 @@ module Hutch
|
|
106
120
|
Hutch::Config.mq_tls = tls
|
107
121
|
end
|
108
122
|
|
109
|
-
opts.on('--mq-tls-cert FILE', 'Certificate
|
110
|
-
|
111
|
-
|
123
|
+
opts.on('--mq-tls-cert FILE', 'Certificate for TLS client verification') do |file|
|
124
|
+
abort_without_file(file, 'Certificate file') do
|
125
|
+
Hutch::Config.mq_tls_cert = file
|
126
|
+
end
|
112
127
|
end
|
113
128
|
|
114
129
|
opts.on('--mq-tls-key FILE', 'Private key for TLS client verification') do |file|
|
115
|
-
|
116
|
-
|
130
|
+
abort_without_file(file, 'Private key file') do
|
131
|
+
Hutch::Config.mq_tls_key = file
|
132
|
+
end
|
117
133
|
end
|
118
134
|
|
119
135
|
opts.on('--mq-exchange EXCHANGE',
|
@@ -151,7 +167,7 @@ module Hutch
|
|
151
167
|
begin
|
152
168
|
File.open(file) { |fp| Hutch::Config.load_from_file(fp) }
|
153
169
|
rescue Errno::ENOENT
|
154
|
-
|
170
|
+
abort_with_message("Config file '#{file}' not found")
|
155
171
|
end
|
156
172
|
end
|
157
173
|
|
@@ -183,6 +199,10 @@ module Hutch
|
|
183
199
|
Hutch::Config.pidfile = pidfile
|
184
200
|
end
|
185
201
|
|
202
|
+
opts.on('--only-group GROUP', 'Load only consumers in this group') do |group|
|
203
|
+
Hutch::Config.group = group
|
204
|
+
end
|
205
|
+
|
186
206
|
opts.on('--version', 'Print the version and exit') do
|
187
207
|
puts "hutch v#{VERSION}"
|
188
208
|
exit 0
|
@@ -201,5 +221,26 @@ module Hutch
|
|
201
221
|
File.open(pidfile, 'w') { |f| f.puts ::Process.pid }
|
202
222
|
end
|
203
223
|
|
224
|
+
private
|
225
|
+
|
226
|
+
def daemonise_process
|
227
|
+
return unless Hutch::Config.daemonise
|
228
|
+
if defined?(JRUBY_VERSION)
|
229
|
+
Hutch.logger.warn "JRuby ignores the --daemonise option"
|
230
|
+
return
|
231
|
+
end
|
232
|
+
|
233
|
+
::Process.daemon(true)
|
234
|
+
end
|
235
|
+
|
236
|
+
def abort_without_file(file, file_description, &block)
|
237
|
+
abort_with_message("#{file_description} '#{file}' not found") unless File.exists?(file)
|
238
|
+
|
239
|
+
yield
|
240
|
+
end
|
241
|
+
|
242
|
+
def abort_with_message(message)
|
243
|
+
abort message
|
244
|
+
end
|
204
245
|
end
|
205
246
|
end
|