hutch 0.25.0.pre.rc1-java → 2.0.0.rc1-java
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/CHANGELOG.md +402 -5
- data/LICENSE +1 -0
- data/README.md +157 -47
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/before_build_docker.sh +15 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +1 -1
- data/lib/hutch/adapters/bunny.rb +29 -1
- data/lib/hutch/adapters/march_hare.rb +24 -0
- data/lib/hutch/broker.rb +122 -36
- data/lib/hutch/cli.rb +23 -12
- data/lib/hutch/config.rb +45 -13
- data/lib/hutch/consumer.rb +63 -4
- data/lib/hutch/error_handlers/airbrake.rb +20 -2
- 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 +11 -3
- data/lib/hutch/error_handlers/logger.rb +7 -2
- data/lib/hutch/error_handlers/rollbar.rb +28 -0
- data/lib/hutch/error_handlers/sentry.rb +13 -11
- data/lib/hutch/error_handlers.rb +2 -1
- data/lib/hutch/publisher.rb +1 -1
- data/lib/hutch/serializers/json.rb +3 -3
- data/lib/hutch/tracers/datadog.rb +18 -0
- data/lib/hutch/tracers.rb +1 -1
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/waiter.rb +1 -1
- data/lib/hutch/worker.rb +74 -8
- data/lib/hutch.rb +8 -4
- data/lib/yard-settings/yard-settings.rb +2 -2
- data/spec/hutch/broker_spec.rb +222 -10
- data/spec/hutch/cli_spec.rb +25 -9
- data/spec/hutch/config_spec.rb +84 -12
- data/spec/hutch/consumer_spec.rb +93 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +23 -1
- data/spec/hutch/error_handlers/bugsnag_spec.rb +56 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +22 -1
- data/spec/hutch/error_handlers/logger_spec.rb +12 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +27 -2
- data/spec/hutch/message_spec.rb +1 -1
- data/spec/hutch/tracers/datadog_spec.rb +46 -0
- data/spec/hutch/waiter_spec.rb +2 -2
- data/spec/hutch/worker_spec.rb +139 -25
- data/spec/integration/channel_recovery_spec.rb +187 -0
- data/spec/integration/publish_consume_spec.rb +47 -0
- metadata +35 -61
- data/.gitignore +0 -10
- data/.rspec +0 -1
- data/.travis.yml +0 -19
- data/.yardopts +0 -5
- data/Gemfile +0 -33
- data/Guardfile +0 -14
- data/Rakefile +0 -21
- data/examples/consumer.rb +0 -13
- data/examples/producer.rb +0 -10
- data/hutch.gemspec +0 -29
- data/lib/hutch/error_handlers/opbeat.rb +0 -24
- data/lib/hutch/tracers/opbeat.rb +0 -37
- data/spec/hutch/error_handlers/opbeat_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -42
- data/spec/tracers/opbeat_spec.rb +0 -44
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
2
|
require 'honeybadger'
|
|
3
|
+
require 'hutch/error_handlers/base'
|
|
3
4
|
|
|
4
5
|
module Hutch
|
|
5
6
|
module ErrorHandlers
|
|
6
7
|
# Error handler for the Honeybadger.io service
|
|
7
|
-
class Honeybadger
|
|
8
|
-
include Logging
|
|
8
|
+
class Honeybadger < Base
|
|
9
9
|
|
|
10
10
|
def handle(properties, payload, consumer, ex)
|
|
11
11
|
message_id = properties.message_id
|
|
@@ -20,6 +20,14 @@ module Hutch
|
|
|
20
20
|
parameters: { payload: payload })
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def handle_setup_exception(ex)
|
|
24
|
+
logger.error "Logging setup exception to Honeybadger"
|
|
25
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
26
|
+
notify_honeybadger(error_class: ex.class.name,
|
|
27
|
+
error_message: "#{ex.class.name}: #{ex.message}",
|
|
28
|
+
backtrace: ex.backtrace)
|
|
29
|
+
end
|
|
30
|
+
|
|
23
31
|
# Wrap API to support 3.0.0+
|
|
24
32
|
#
|
|
25
33
|
# @see https://github.com/honeybadger-io/honeybadger-ruby/blob/master/CHANGELOG.md#300---2017-02-06
|
|
@@ -27,7 +35,7 @@ module Hutch
|
|
|
27
35
|
if ::Honeybadger.respond_to?(:notify_or_ignore)
|
|
28
36
|
::Honeybadger.notify_or_ignore(message)
|
|
29
37
|
else
|
|
30
|
-
::Honeybadger.notify(message
|
|
38
|
+
::Honeybadger.notify(message)
|
|
31
39
|
end
|
|
32
40
|
end
|
|
33
41
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
|
+
require 'hutch/error_handlers/base'
|
|
2
3
|
|
|
3
4
|
module Hutch
|
|
4
5
|
module ErrorHandlers
|
|
5
|
-
class Logger
|
|
6
|
-
include Logging
|
|
6
|
+
class Logger < ErrorHandlers::Base
|
|
7
7
|
|
|
8
8
|
def handle(properties, payload, consumer, ex)
|
|
9
9
|
message_id = properties.message_id
|
|
@@ -12,6 +12,11 @@ module Hutch
|
|
|
12
12
|
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
13
13
|
logger.error (['backtrace:'] + ex.backtrace).join("\n")
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
def handle_setup_exception(ex)
|
|
17
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
18
|
+
logger.error (['backtrace:'] + ex.backtrace).join("\n")
|
|
19
|
+
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
17
22
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'hutch/logging'
|
|
2
|
+
require 'rollbar'
|
|
3
|
+
require 'hutch/error_handlers/base'
|
|
4
|
+
|
|
5
|
+
module Hutch
|
|
6
|
+
module ErrorHandlers
|
|
7
|
+
class Rollbar < Base
|
|
8
|
+
def handle(properties, payload, consumer, ex)
|
|
9
|
+
message_id = properties.message_id
|
|
10
|
+
prefix = "message(#{message_id || '-'}):"
|
|
11
|
+
logger.error "#{prefix} Logging event to Rollbar"
|
|
12
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
13
|
+
|
|
14
|
+
::Rollbar.error(ex,
|
|
15
|
+
payload: payload,
|
|
16
|
+
consumer: consumer
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def handle_setup_exception(ex)
|
|
21
|
+
logger.error "Logging setup exception to Rollbar"
|
|
22
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
23
|
+
|
|
24
|
+
::Rollbar.error(ex)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
|
-
require '
|
|
2
|
+
require 'sentry-ruby'
|
|
3
|
+
require 'hutch/error_handlers/base'
|
|
3
4
|
|
|
4
5
|
module Hutch
|
|
5
6
|
module ErrorHandlers
|
|
6
|
-
class Sentry
|
|
7
|
-
include Logging
|
|
8
|
-
|
|
9
|
-
def initialize
|
|
10
|
-
unless Raven.respond_to?(:capture_exception)
|
|
11
|
-
raise "The Hutch Sentry error handler requires Raven >= 0.4.0"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
7
|
+
class Sentry < Base
|
|
15
8
|
def handle(properties, payload, consumer, ex)
|
|
16
9
|
message_id = properties.message_id
|
|
17
10
|
prefix = "message(#{message_id || '-'}):"
|
|
18
11
|
logger.error "#{prefix} Logging event to Sentry"
|
|
19
12
|
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
20
|
-
|
|
13
|
+
::Sentry.configure_scope do |scope|
|
|
14
|
+
scope.set_context("payload", JSON.parse(payload))
|
|
15
|
+
end
|
|
16
|
+
::Sentry.capture_exception(ex)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def handle_setup_exception(ex)
|
|
20
|
+
logger.error "Logging setup exception to Sentry"
|
|
21
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
22
|
+
::Sentry.capture_exception(ex)
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
end
|
data/lib/hutch/error_handlers.rb
CHANGED
|
@@ -4,6 +4,7 @@ module Hutch
|
|
|
4
4
|
autoload :Sentry, 'hutch/error_handlers/sentry'
|
|
5
5
|
autoload :Honeybadger, 'hutch/error_handlers/honeybadger'
|
|
6
6
|
autoload :Airbrake, 'hutch/error_handlers/airbrake'
|
|
7
|
-
autoload :
|
|
7
|
+
autoload :Rollbar, 'hutch/error_handlers/rollbar'
|
|
8
|
+
autoload :Bugsnag, 'hutch/error_handlers/bugsnag'
|
|
8
9
|
end
|
|
9
10
|
end
|
data/lib/hutch/publisher.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'json'
|
|
2
2
|
require 'active_support/core_ext/hash/indifferent_access'
|
|
3
3
|
|
|
4
4
|
module Hutch
|
|
@@ -6,11 +6,11 @@ module Hutch
|
|
|
6
6
|
class JSON
|
|
7
7
|
|
|
8
8
|
def self.encode(payload)
|
|
9
|
-
::JSON.
|
|
9
|
+
::JSON.generate(payload)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def self.decode(payload)
|
|
13
|
-
::
|
|
13
|
+
::JSON.parse(payload).with_indifferent_access
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.binary? ; false ; end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'datadog'
|
|
2
|
+
require 'datadog/auto_instrument'
|
|
3
|
+
|
|
4
|
+
module Hutch
|
|
5
|
+
module Tracers
|
|
6
|
+
class Datadog
|
|
7
|
+
def initialize(klass)
|
|
8
|
+
@klass = klass
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def handle(message)
|
|
12
|
+
::Datadog::Tracing.trace(@klass.class.name, continue_from: nil, service: 'hutch', type: 'rabbitmq') do
|
|
13
|
+
@klass.process(message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/hutch/tracers.rb
CHANGED
data/lib/hutch/version.rb
CHANGED
data/lib/hutch/waiter.rb
CHANGED
data/lib/hutch/worker.rb
CHANGED
|
@@ -11,8 +11,9 @@ module Hutch
|
|
|
11
11
|
include Logging
|
|
12
12
|
|
|
13
13
|
def initialize(broker, consumers, setup_procs)
|
|
14
|
-
@broker
|
|
15
|
-
|
|
14
|
+
@broker = broker
|
|
15
|
+
@recovery_lock = Mutex.new
|
|
16
|
+
self.consumers = consumers
|
|
16
17
|
self.setup_procs = setup_procs
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -36,21 +37,62 @@ module Hutch
|
|
|
36
37
|
# Set up the queues for each of the worker's consumers.
|
|
37
38
|
def setup_queues
|
|
38
39
|
logger.info 'setting up queues'
|
|
39
|
-
@consumers.
|
|
40
|
+
vetted = @consumers.reject { |c| group_configured? && group_restricted?(c) }
|
|
41
|
+
vetted.each do |c|
|
|
42
|
+
setup_queue(c)
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Bind a consumer's routing keys to its queue, and set up a subscription to
|
|
43
47
|
# receive messages sent to the queue.
|
|
44
48
|
def setup_queue(consumer)
|
|
45
|
-
|
|
49
|
+
queue_name = consumer.get_queue_name
|
|
50
|
+
queue_name = @broker.namespaced_queue_name(queue_name) unless consumer.without_namespace?
|
|
51
|
+
logger.info "setting up queue: #{queue_name}"
|
|
52
|
+
|
|
53
|
+
queue = @broker.queue(queue_name, consumer.get_options)
|
|
46
54
|
@broker.bind_queue(queue, consumer.routing_keys)
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
on_cancellation = proc { handle_cancellation(queue_name, queue.channel) }
|
|
57
|
+
queue.subscribe(consumer_tag: unique_consumer_tag, manual_ack: true,
|
|
58
|
+
on_cancellation: on_cancellation) do |*args|
|
|
49
59
|
delivery_info, properties, payload = Hutch::Adapter.decode_message(*args)
|
|
50
60
|
handle_message(consumer, delivery_info, properties, payload)
|
|
51
61
|
end
|
|
52
62
|
end
|
|
53
63
|
|
|
64
|
+
# A server-sent `basic.cancel` carries no reason, so a consumer timeout
|
|
65
|
+
# is indistinguishable from a queue deletion. Only the former is recoverable.
|
|
66
|
+
def handle_cancellation(queue_name, cancelled_channel)
|
|
67
|
+
unless @broker.queue_exists?(queue_name)
|
|
68
|
+
logger.error "consumer on queue #{queue_name} was cancelled by the server: the queue no longer exists"
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
logger.warn "consumer on queue #{queue_name} was cancelled by the server, re-subscribing"
|
|
73
|
+
resubscribe_on_a_new_channel(cancelled_channel)
|
|
74
|
+
rescue => ex
|
|
75
|
+
logger.error "consumer re-subscription failed: #{ex.class}: #{ex.message}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Runs on its own thread: closing the channel kills the consumer work
|
|
79
|
+
# pool this callback runs on. Returns the thread so that tests can join it.
|
|
80
|
+
def resubscribe_on_a_new_channel(cancelled_channel)
|
|
81
|
+
Thread.new do
|
|
82
|
+
begin
|
|
83
|
+
@recovery_lock.synchronize do
|
|
84
|
+
# All consumers share the channel, so only the first cancellation replaces it.
|
|
85
|
+
next unless @broker.channel.equal?(cancelled_channel)
|
|
86
|
+
|
|
87
|
+
@broker.replace_channel!
|
|
88
|
+
setup_queues
|
|
89
|
+
end
|
|
90
|
+
rescue => ex
|
|
91
|
+
logger.error "consumer re-subscription failed: #{ex.class}: #{ex.message}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
54
96
|
# Called internally when a new messages comes in from RabbitMQ. Responsible
|
|
55
97
|
# for wrapping up the message and passing it to the consumer.
|
|
56
98
|
def handle_message(consumer, delivery_info, properties, payload)
|
|
@@ -66,10 +108,10 @@ module Hutch
|
|
|
66
108
|
message = Message.new(delivery_info, properties, payload, serializer)
|
|
67
109
|
consumer_instance = consumer.new.tap { |c| c.broker, c.delivery_info = @broker, delivery_info }
|
|
68
110
|
with_tracing(consumer_instance).handle(message)
|
|
69
|
-
@broker.ack(delivery_info.delivery_tag)
|
|
111
|
+
@broker.ack(delivery_info.delivery_tag, channel: delivery_info.channel) unless consumer_instance.message_rejected?
|
|
70
112
|
rescue => ex
|
|
71
113
|
acknowledge_error(delivery_info, properties, @broker, ex)
|
|
72
|
-
handle_error(properties, payload, consumer, ex)
|
|
114
|
+
handle_error(properties, payload, consumer, ex, delivery_info)
|
|
73
115
|
end
|
|
74
116
|
|
|
75
117
|
def with_tracing(klass)
|
|
@@ -78,7 +120,7 @@ module Hutch
|
|
|
78
120
|
|
|
79
121
|
def handle_error(*args)
|
|
80
122
|
Hutch::Config[:error_handlers].each do |backend|
|
|
81
|
-
backend.handle
|
|
123
|
+
backend.handle *args.first(backend.method(:handle).arity)
|
|
82
124
|
end
|
|
83
125
|
end
|
|
84
126
|
|
|
@@ -103,6 +145,30 @@ module Hutch
|
|
|
103
145
|
|
|
104
146
|
private
|
|
105
147
|
|
|
148
|
+
def group_configured?
|
|
149
|
+
if group.present? && consumer_groups.blank?
|
|
150
|
+
logger.info 'Consumer groups are blank'
|
|
151
|
+
end
|
|
152
|
+
group.present?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def group_restricted?(consumer)
|
|
156
|
+
consumers_to_load = consumer_groups[group]
|
|
157
|
+
if consumers_to_load
|
|
158
|
+
!consumers_to_load.include?(consumer.name)
|
|
159
|
+
else
|
|
160
|
+
true
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def group
|
|
165
|
+
Hutch::Config[:group]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def consumer_groups
|
|
169
|
+
Hutch::Config[:consumer_groups]
|
|
170
|
+
end
|
|
171
|
+
|
|
106
172
|
attr_accessor :setup_procs
|
|
107
173
|
|
|
108
174
|
def unique_consumer_tag
|
data/lib/hutch.rb
CHANGED
|
@@ -14,6 +14,8 @@ require 'hutch/exceptions'
|
|
|
14
14
|
require 'hutch/tracers'
|
|
15
15
|
|
|
16
16
|
module Hutch
|
|
17
|
+
@@connection_mutex = Mutex.new
|
|
18
|
+
|
|
17
19
|
def self.register_consumer(consumer)
|
|
18
20
|
self.consumers << consumer
|
|
19
21
|
end
|
|
@@ -40,10 +42,12 @@ module Hutch
|
|
|
40
42
|
# @param config [Hash] Configuration
|
|
41
43
|
# @option options [Boolean] :enable_http_api_use
|
|
42
44
|
def self.connect(options = {}, config = Hutch::Config)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
@@connection_mutex.synchronize do
|
|
46
|
+
unless connected?
|
|
47
|
+
@broker = Hutch::Broker.new(config)
|
|
48
|
+
@broker.connect(options)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def self.disconnect
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
YARD::Templates::Engine.register_template_path(File.
|
|
2
|
-
|
|
1
|
+
YARD::Templates::Engine.register_template_path(File.join(__dir__, '../../templates'))
|
|
2
|
+
require_relative 'handler'
|
data/spec/hutch/broker_spec.rb
CHANGED
|
@@ -92,6 +92,110 @@ describe Hutch::Broker do
|
|
|
92
92
|
|
|
93
93
|
connection.close
|
|
94
94
|
end
|
|
95
|
+
|
|
96
|
+
context 'when configured with a URI' do
|
|
97
|
+
context 'which specifies the port' do
|
|
98
|
+
before { config[:uri] = "amqp://#{config[:mq_username]}:#{config[:mq_password]}@#{config[:mq_host]}:#{config[:mq_port]}/" }
|
|
99
|
+
|
|
100
|
+
it 'successfully connects' do
|
|
101
|
+
c = broker.open_connection
|
|
102
|
+
expect(c).to be_open
|
|
103
|
+
c.close
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'which does not specify port and uses the amqp scheme' do
|
|
108
|
+
before { config[:uri] = "amqp://#{config[:mq_username]}:#{config[:mq_password]}@#{config[:mq_host]}/" }
|
|
109
|
+
|
|
110
|
+
it 'successfully connects' do
|
|
111
|
+
c = broker.open_connection
|
|
112
|
+
expect(c).to be_open
|
|
113
|
+
c.close
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'which specifies the amqps scheme' do
|
|
118
|
+
before { config[:uri] = "amqps://#{config[:mq_username]}:#{config[:mq_password]}@#{config[:mq_host]}/" }
|
|
119
|
+
|
|
120
|
+
it 'utilises TLS' do
|
|
121
|
+
expect(Hutch::Adapter).to receive(:new).with(
|
|
122
|
+
hash_including(tls: true)
|
|
123
|
+
).and_return(instance_double('Hutch::Adapter', start: nil))
|
|
124
|
+
|
|
125
|
+
broker.open_connection
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe '#parse_uri' do
|
|
132
|
+
{
|
|
133
|
+
"amqp://host/vhost" => "vhost",
|
|
134
|
+
"amqp://host/v%2fhost" => "v/host",
|
|
135
|
+
"amqp://host/%2f" => "/",
|
|
136
|
+
"amqp://host/" => Hutch::Adapter::DEFAULT_VHOST,
|
|
137
|
+
"amqp://host" => Hutch::Adapter::DEFAULT_VHOST,
|
|
138
|
+
}.each do |uri, vhost|
|
|
139
|
+
it "parses the vhost of #{uri} as #{vhost.inspect}" do
|
|
140
|
+
config[:uri] = uri
|
|
141
|
+
broker.send(:parse_uri)
|
|
142
|
+
|
|
143
|
+
expect(config[:mq_vhost]).to eq(vhost)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'passes the vhost to the connection unchanged' do
|
|
148
|
+
config[:uri] = "amqp://host/%2f"
|
|
149
|
+
|
|
150
|
+
expect(broker.send(:connection_params)[:vhost]).to eq("/")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'percent-decodes the username and password' do
|
|
154
|
+
config[:uri] = "amqp://al%23pha:be%20ta@host:10000/vhost"
|
|
155
|
+
broker.send(:parse_uri)
|
|
156
|
+
|
|
157
|
+
expect(config[:mq_username]).to eq("al#pha")
|
|
158
|
+
expect(config[:mq_password]).to eq("be ta")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'leaves absent credentials nil' do
|
|
162
|
+
config[:uri] = "amqp://host:10000/vhost"
|
|
163
|
+
broker.send(:parse_uri)
|
|
164
|
+
|
|
165
|
+
expect(config[:mq_username]).to be_nil
|
|
166
|
+
expect(config[:mq_password]).to be_nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe '#bindings', 'vhost filtering' do
|
|
171
|
+
let(:api_bindings) do
|
|
172
|
+
[{ 'source' => 'hutch', 'vhost' => '/', 'destination' => 'q', 'routing_key' => 'key' }]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
before do
|
|
176
|
+
config[:mq_exchange] = 'hutch'
|
|
177
|
+
broker.api_client = double('api_client', bindings: api_bindings)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'filters on the default vhost when the configured vhost is blank' do
|
|
181
|
+
config[:mq_vhost] = ''
|
|
182
|
+
|
|
183
|
+
expect(broker.bindings).to eq('q' => ['key'])
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
describe '#sanitized_uri' do
|
|
188
|
+
it 'percent-encodes the decoded username and vhost again' do
|
|
189
|
+
config[:uri] = 'amqp://al%23pha:be%20ta@host:10000/v%2Fhost'
|
|
190
|
+
|
|
191
|
+
expect(broker.send(:sanitized_uri)).to eq('amqp://al%23pha@host:10000/v%2Fhost')
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'renders the default vhost as a bare trailing slash' do
|
|
195
|
+
config[:uri] = 'amqp://alpha:beta@host:10000/'
|
|
196
|
+
|
|
197
|
+
expect(broker.send(:sanitized_uri)).to eq('amqp://alpha@host:10000/')
|
|
198
|
+
end
|
|
95
199
|
end
|
|
96
200
|
|
|
97
201
|
describe '#open_connection!' do
|
|
@@ -165,6 +269,100 @@ describe Hutch::Broker do
|
|
|
165
269
|
end
|
|
166
270
|
end
|
|
167
271
|
|
|
272
|
+
describe '#replace_channel!', adapter: :bunny do
|
|
273
|
+
let(:old_channel) { double('Bunny::Channel', open?: true, close: nil) }
|
|
274
|
+
let(:new_channel) { double('Bunny::Channel').as_null_object }
|
|
275
|
+
let(:work_pool) { double('Bunny::ConsumerWorkPool', shutdown: nil, join: nil, kill: nil) }
|
|
276
|
+
|
|
277
|
+
before do
|
|
278
|
+
broker.channel = old_channel
|
|
279
|
+
allow(broker).to receive(:channel_work_pool).and_return(work_pool)
|
|
280
|
+
allow(broker).to receive(:open_channel).and_return(new_channel)
|
|
281
|
+
allow(broker).to receive(:declare_exchange!)
|
|
282
|
+
allow(broker).to receive(:declare_publisher!)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'drains the work pool before closing the old channel' do
|
|
286
|
+
expect(work_pool).to receive(:shutdown).ordered
|
|
287
|
+
expect(work_pool).to receive(:join).with(config[:graceful_exit_timeout]).ordered
|
|
288
|
+
expect(work_pool).to receive(:kill).ordered
|
|
289
|
+
expect(old_channel).to receive(:close).ordered
|
|
290
|
+
|
|
291
|
+
broker.replace_channel!
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
it 'redeclares the exchange and the publisher on the new channel' do
|
|
295
|
+
expect(broker).to receive(:declare_exchange!)
|
|
296
|
+
expect(broker).to receive(:declare_publisher!)
|
|
297
|
+
|
|
298
|
+
broker.replace_channel!
|
|
299
|
+
|
|
300
|
+
expect(broker.channel).to eq(new_channel)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
context 'when the old channel is already closed' do
|
|
304
|
+
let(:old_channel) { double('Bunny::Channel', open?: false) }
|
|
305
|
+
|
|
306
|
+
it 'opens a new channel anyway' do
|
|
307
|
+
expect(old_channel).not_to receive(:close)
|
|
308
|
+
expect(work_pool).not_to receive(:shutdown)
|
|
309
|
+
|
|
310
|
+
broker.replace_channel!
|
|
311
|
+
|
|
312
|
+
expect(broker.channel).to eq(new_channel)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
context 'when the server closes the old channel concurrently' do
|
|
317
|
+
it 'opens a new channel anyway' do
|
|
318
|
+
allow(old_channel).to receive(:close).
|
|
319
|
+
and_raise(Hutch::Adapter::ChannelAlreadyClosed.new('already closed', old_channel))
|
|
320
|
+
|
|
321
|
+
broker.replace_channel!
|
|
322
|
+
|
|
323
|
+
expect(broker.channel).to eq(new_channel)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
describe 'acknowledgements' do
|
|
329
|
+
let(:current_channel) { double('Channel').as_null_object }
|
|
330
|
+
let(:superseded_channel) { double('Channel') }
|
|
331
|
+
|
|
332
|
+
before { broker.channel = current_channel }
|
|
333
|
+
|
|
334
|
+
it 'sends them on the channel the delivery arrived on' do
|
|
335
|
+
expect(current_channel).to receive(:ack).with('dt', false)
|
|
336
|
+
broker.ack('dt', channel: current_channel)
|
|
337
|
+
|
|
338
|
+
expect(current_channel).to receive(:nack).with('dt', false, false)
|
|
339
|
+
broker.nack('dt', channel: current_channel)
|
|
340
|
+
|
|
341
|
+
expect(current_channel).to receive(:reject).with('dt', false)
|
|
342
|
+
broker.reject('dt', channel: current_channel)
|
|
343
|
+
|
|
344
|
+
expect(current_channel).to receive(:reject).with('dt', true)
|
|
345
|
+
broker.requeue('dt', channel: current_channel)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
it 'drops them when the channel has been replaced' do
|
|
349
|
+
expect(superseded_channel).not_to receive(:ack)
|
|
350
|
+
expect(superseded_channel).not_to receive(:nack)
|
|
351
|
+
expect(superseded_channel).not_to receive(:reject)
|
|
352
|
+
|
|
353
|
+
broker.ack('dt', channel: superseded_channel)
|
|
354
|
+
broker.nack('dt', channel: superseded_channel)
|
|
355
|
+
broker.reject('dt', channel: superseded_channel)
|
|
356
|
+
broker.requeue('dt', channel: superseded_channel)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
it 'defaults to the current channel' do
|
|
360
|
+
expect(current_channel).to receive(:ack).with('dt', false)
|
|
361
|
+
|
|
362
|
+
broker.ack('dt')
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
168
366
|
describe '#declare_exchange' do
|
|
169
367
|
before do
|
|
170
368
|
broker.open_connection!
|
|
@@ -211,9 +409,9 @@ describe Hutch::Broker do
|
|
|
211
409
|
context 'when given invalid details' do
|
|
212
410
|
before { config[:mq_api_host] = 'notarealhost' }
|
|
213
411
|
after { broker.disconnect }
|
|
214
|
-
let(:set_up_api_connection) {
|
|
412
|
+
let(:set_up_api_connection) { broker.set_up_api_connection }
|
|
215
413
|
|
|
216
|
-
specify { expect
|
|
414
|
+
specify { expect { broker.set_up_api_connection }.to raise_error(StandardError) }
|
|
217
415
|
end
|
|
218
416
|
end
|
|
219
417
|
|
|
@@ -222,13 +420,27 @@ describe Hutch::Broker do
|
|
|
222
420
|
let(:arguments) { { foo: :bar } }
|
|
223
421
|
before { allow(broker).to receive(:channel) { channel } }
|
|
224
422
|
|
|
225
|
-
it '
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
423
|
+
it 'creates a queue with the given name' do
|
|
424
|
+
expect(broker.channel).to receive(:queue).with('test', arguments: arguments)
|
|
425
|
+
broker.queue('test'.freeze, arguments: arguments)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
describe '#namespaced_queue_name' do
|
|
430
|
+
context 'with a namespace configured' do
|
|
431
|
+
before { config[:namespace] = 'mirror-all.service' }
|
|
432
|
+
|
|
433
|
+
it 'prepends the namespace' do
|
|
434
|
+
expect(broker.namespaced_queue_name('test')).to eq('mirror-all.service:test')
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
context 'without a namespace configured' do
|
|
439
|
+
before { config[:namespace] = nil }
|
|
440
|
+
|
|
441
|
+
it 'returns the name unchanged' do
|
|
442
|
+
expect(broker.namespaced_queue_name('test')).to eq('test')
|
|
230
443
|
end
|
|
231
|
-
broker.queue('test', arguments)
|
|
232
444
|
end
|
|
233
445
|
end
|
|
234
446
|
|
|
@@ -245,7 +457,7 @@ describe Hutch::Broker do
|
|
|
245
457
|
|
|
246
458
|
context 'with a binding' do
|
|
247
459
|
around do |example|
|
|
248
|
-
queue = broker.queue('test').bind(broker.exchange, routing_key: 'key')
|
|
460
|
+
queue = broker.queue('test', durable: true).bind(broker.exchange, routing_key: 'key')
|
|
249
461
|
example.run
|
|
250
462
|
queue.unbind(broker.exchange, routing_key: 'key').delete
|
|
251
463
|
end
|
|
@@ -275,7 +487,7 @@ describe Hutch::Broker do
|
|
|
275
487
|
end
|
|
276
488
|
|
|
277
489
|
context '(rabbitmq integration test)', rabbitmq: true do
|
|
278
|
-
let(:queue) { broker.queue('consumer') }
|
|
490
|
+
let(:queue) { broker.queue('consumer', durable: true) }
|
|
279
491
|
let(:routing_key) { 'key' }
|
|
280
492
|
|
|
281
493
|
before { allow(broker).to receive(:bindings).and_call_original }
|