shivam 0.0.0-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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +57 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +899 -0
  7. data/Gemfile +35 -0
  8. data/Guardfile +14 -0
  9. data/LICENSE +23 -0
  10. data/README.md +679 -0
  11. data/Rakefile +21 -0
  12. data/bin/ci/before_build.sh +20 -0
  13. data/bin/ci/before_build_docker.sh +20 -0
  14. data/bin/ci/install_on_debian.sh +46 -0
  15. data/bin/hutch +8 -0
  16. data/examples/consumer.rb +13 -0
  17. data/examples/producer.rb +10 -0
  18. data/hutch.gemspec +27 -0
  19. data/lib/hutch/acknowledgements/base.rb +16 -0
  20. data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
  21. data/lib/hutch/adapter.rb +11 -0
  22. data/lib/hutch/adapters/bunny.rb +37 -0
  23. data/lib/hutch/adapters/march_hare.rb +41 -0
  24. data/lib/hutch/broker.rb +384 -0
  25. data/lib/hutch/cli.rb +246 -0
  26. data/lib/hutch/config.rb +305 -0
  27. data/lib/hutch/consumer.rb +125 -0
  28. data/lib/hutch/error_handlers/airbrake.rb +54 -0
  29. data/lib/hutch/error_handlers/base.rb +15 -0
  30. data/lib/hutch/error_handlers/bugsnag.rb +30 -0
  31. data/lib/hutch/error_handlers/honeybadger.rb +43 -0
  32. data/lib/hutch/error_handlers/logger.rb +22 -0
  33. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  34. data/lib/hutch/error_handlers/sentry.rb +26 -0
  35. data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
  36. data/lib/hutch/error_handlers.rb +11 -0
  37. data/lib/hutch/exceptions.rb +14 -0
  38. data/lib/hutch/logging.rb +32 -0
  39. data/lib/hutch/message.rb +31 -0
  40. data/lib/hutch/publisher.rb +75 -0
  41. data/lib/hutch/serializers/identity.rb +19 -0
  42. data/lib/hutch/serializers/json.rb +22 -0
  43. data/lib/hutch/tracers/datadog.rb +18 -0
  44. data/lib/hutch/tracers/newrelic.rb +19 -0
  45. data/lib/hutch/tracers/null_tracer.rb +15 -0
  46. data/lib/hutch/tracers.rb +7 -0
  47. data/lib/hutch/version.rb +3 -0
  48. data/lib/hutch/waiter.rb +104 -0
  49. data/lib/hutch/worker.rb +145 -0
  50. data/lib/hutch.rb +69 -0
  51. data/lib/yard-settings/handler.rb +38 -0
  52. data/lib/yard-settings/yard-settings.rb +2 -0
  53. data/spec/hutch/broker_spec.rb +462 -0
  54. data/spec/hutch/cli_spec.rb +93 -0
  55. data/spec/hutch/config_spec.rb +259 -0
  56. data/spec/hutch/consumer_spec.rb +208 -0
  57. data/spec/hutch/error_handlers/airbrake_spec.rb +49 -0
  58. data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
  59. data/spec/hutch/error_handlers/honeybadger_spec.rb +58 -0
  60. data/spec/hutch/error_handlers/logger_spec.rb +28 -0
  61. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  62. data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
  63. data/spec/hutch/error_handlers/sentry_spec.rb +47 -0
  64. data/spec/hutch/logger_spec.rb +34 -0
  65. data/spec/hutch/message_spec.rb +38 -0
  66. data/spec/hutch/serializers/json_spec.rb +17 -0
  67. data/spec/hutch/tracers/datadog_spec.rb +44 -0
  68. data/spec/hutch/waiter_spec.rb +51 -0
  69. data/spec/hutch/worker_spec.rb +184 -0
  70. data/spec/hutch_spec.rb +87 -0
  71. data/spec/spec_helper.rb +42 -0
  72. data/templates/default/class/html/settings.erb +0 -0
  73. data/templates/default/class/setup.rb +4 -0
  74. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  75. data/templates/default/layout/html/setup.rb +7 -0
  76. data/templates/default/method_details/html/settings.erb +5 -0
  77. data/templates/default/method_details/setup.rb +4 -0
  78. data/templates/default/method_details/text/settings.erb +0 -0
  79. data/templates/default/module/html/settings.erb +40 -0
  80. data/templates/default/module/setup.rb +4 -0
  81. metadata +205 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env sh
2
+
3
+ CTL=${BUNNY_RABBITMQCTL:-"sudo rabbitmqctl"}
4
+ PLUGINS=${BUNNY_RABBITMQ_PLUGINS:-"sudo rabbitmq-plugins"}
5
+
6
+ echo "Will use rabbitmqctl at ${CTL}"
7
+ echo "Will use rabbitmq-plugins at ${PLUGINS}"
8
+
9
+ $PLUGINS enable rabbitmq_management
10
+
11
+ sleep 3
12
+
13
+ # guest:guest has full access to /
14
+ $CTL add_vhost /
15
+ $CTL add_user guest guest
16
+ $CTL set_permissions -p / guest ".*" ".*" ".*"
17
+
18
+ # Reduce retention policy for faster publishing of stats
19
+ $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_sup_sup, rabbit_mgmt_sup), application:set_env(rabbitmq_management, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_sup_sup:start_child().' || true
20
+ $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_agent_sup_sup, rabbit_mgmt_agent_sup), application:set_env(rabbitmq_management_agent, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_agent_sup_sup:start_child().' || true
@@ -0,0 +1,20 @@
1
+ #!/bin/sh
2
+
3
+ CTL=${MARCH_HARE_RABBITMQCTL:="docker exec rabbitmq rabbitmqctl"}
4
+ PLUGINS=${MARCH_HARE_RABBITMQ_PLUGINS:="docker exec rabbitmq rabbitmq-plugins"}
5
+
6
+ $PLUGINS enable rabbitmq_management
7
+
8
+ sleep 3
9
+
10
+ # guest:guest has full access to /
11
+
12
+ $CTL add_vhost /
13
+ # $CTL add_user guest guest # already exists
14
+ $CTL set_permissions -p / guest ".*" ".*" ".*"
15
+
16
+ # Reduce retention policy for faster publishing of stats
17
+ $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_sup_sup, rabbit_mgmt_sup), application:set_env(rabbitmq_management, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_sup_sup:start_child().'
18
+ $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_agent_sup_sup, rabbit_mgmt_agent_sup), application:set_env(rabbitmq_management_agent, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_agent_sup_sup:start_child().'
19
+
20
+ sleep 3
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+
3
+ sudo apt-get install curl gnupg debian-keyring debian-archive-keyring apt-transport-https -y
4
+
5
+ ## Team RabbitMQ's main signing key
6
+ sudo apt-key adv --keyserver "hkps://keys.openpgp.org" --recv-keys "0x0A9AF2115F4687BD29803A206B73A36E6026DFCA"
7
+ ## Launchpad PPA that provides modern Erlang releases
8
+ sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F77F1EDA57EBB1CC"
9
+ ## PackageCloud RabbitMQ repository
10
+ sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F6609E60DC62814E"
11
+
12
+ ## Add apt repositories maintained by Team RabbitMQ
13
+ sudo tee /etc/apt/sources.list.d/rabbitmq.list <<EOF
14
+ ## Provides modern Erlang/OTP releases
15
+ ##
16
+ ## "bionic" as distribution name should work for any reasonably recent Ubuntu or Debian release.
17
+ ## See the release to distribution mapping table in RabbitMQ doc guides to learn more.
18
+ deb http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu bionic main
19
+ deb-src http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu bionic main
20
+
21
+ ## Provides RabbitMQ
22
+ ##
23
+ ## "bionic" as distribution name should work for any reasonably recent Ubuntu or Debian release.
24
+ ## See the release to distribution mapping table in RabbitMQ doc guides to learn more.
25
+ deb https://packagecloud.io/rabbitmq/rabbitmq-server/ubuntu/ bionic main
26
+ deb-src https://packagecloud.io/rabbitmq/rabbitmq-server/ubuntu/ bionic main
27
+ EOF
28
+
29
+ ## Update package indices
30
+ sudo apt-get update -y
31
+
32
+ ## Install Erlang packages
33
+ sudo apt-get install -y erlang-base \
34
+ erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
35
+ erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
36
+ erlang-runtime-tools erlang-snmp erlang-ssl \
37
+ erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl
38
+
39
+ ## Install rabbitmq-server and its dependencies
40
+ sudo apt-get install rabbitmq-server -y --fix-missing
41
+
42
+ sudo service rabbitmq-server start
43
+
44
+ sudo rabbitmqctl await_startup --timeout 120
45
+
46
+ until sudo lsof -i:5672; do echo "Waiting for RabbitMQ to start..."; sleep 1; done
data/bin/hutch ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/hutch'
4
+ require_relative '../lib/hutch/cli'
5
+
6
+ cli = Hutch::CLI.new
7
+ cli.run(ARGV)
8
+
@@ -0,0 +1,13 @@
1
+ require 'hutch'
2
+
3
+ class TestConsumer
4
+ include Hutch::Consumer
5
+ consume 'hutch.test'
6
+
7
+ def process(message)
8
+ puts "TestConsumer got a message: #{message}"
9
+ puts "Processing..."
10
+ sleep(1)
11
+ puts "Done"
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ require 'hutch'
2
+
3
+ Hutch.connect
4
+ loop do
5
+ print "Press return to send test message..."
6
+ gets
7
+ Hutch.publish('hutch.test', subject: 'test message')
8
+ puts "Send message with routing key 'hutch.test'"
9
+ end
10
+
data/hutch.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require File.expand_path('../lib/hutch/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ if defined?(JRUBY_VERSION)
5
+ gem.platform = 'java'
6
+ gem.add_runtime_dependency 'march_hare', '>= 3.0.0'
7
+ else
8
+ gem.platform = Gem::Platform::RUBY
9
+ gem.add_runtime_dependency 'bunny', '>= 2.20', '< 3.0'
10
+ end
11
+ gem.add_runtime_dependency 'carrot-top', '~> 0.0.7'
12
+ gem.add_runtime_dependency 'multi_json', '~> 1.15'
13
+ gem.add_runtime_dependency 'activesupport', '>= 4.2', '< 8'
14
+
15
+ gem.name = 'shivam'
16
+ gem.summary = 'Opinionated asynchronous inter-service communication using RabbitMQ'
17
+ gem.description = 'Hutch is a Ruby library for enabling asynchronous inter-service communication using RabbitMQ'
18
+ gem.version = "0.0.0"
19
+ gem.required_ruby_version = '>= 2.6'
20
+ gem.authors = ['Harry Marr', 'Michael Klishin']
21
+ gem.homepage = 'https://github.com/nulllvoid/hutch'
22
+ gem.require_paths = ['lib']
23
+ gem.license = 'MIT'
24
+ gem.executables = ['hutch']
25
+ gem.files = `git ls-files`.split("\n")
26
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
27
+ end
@@ -0,0 +1,16 @@
1
+ module Hutch
2
+ module Acknowledgements
3
+ # Defines acknowledgement handler interface.
4
+ class Base
5
+ # Implements negative acknowledgement/requeueing logic
6
+ # and returns a boolean to indicate whether acknowledgement
7
+ # was performed. If false is returned, next handler in the
8
+ # chain will be invoked.
9
+ #
10
+ # The chain always falls back to unconditional nacking.
11
+ def handle(delivery_info, properties, broker, ex)
12
+ raise NotImplementedError.new
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'hutch/logging'
2
+ require 'hutch/acknowledgements/base'
3
+
4
+ module Hutch
5
+ module Acknowledgements
6
+ class NackOnAllFailures < Base
7
+ include Logging
8
+
9
+ def handle(delivery_info, properties, broker, ex)
10
+ prefix = "message(#{properties.message_id || '-'}): "
11
+ logger.debug "#{prefix} nacking message"
12
+
13
+ broker.nack(delivery_info.delivery_tag)
14
+
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ if defined?(JRUBY_VERSION)
2
+ require 'hutch/adapters/march_hare'
3
+ module Hutch
4
+ Adapter = Adapters::MarchHareAdapter
5
+ end
6
+ else
7
+ require 'hutch/adapters/bunny'
8
+ module Hutch
9
+ Adapter = Adapters::BunnyAdapter
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'bunny'
2
+ require 'forwardable'
3
+
4
+ module Hutch
5
+ module Adapters
6
+ class BunnyAdapter
7
+ extend Forwardable
8
+
9
+ DEFAULT_VHOST = Bunny::Session::DEFAULT_VHOST
10
+
11
+ ConnectionRefused = Bunny::TCPConnectionFailed
12
+ PreconditionFailed = Bunny::PreconditionFailed
13
+
14
+ def_delegators :@connection, :start, :disconnect, :close, :create_channel, :open?
15
+
16
+ def initialize(opts={})
17
+ @connection = Bunny.new(opts)
18
+ end
19
+
20
+ def self.decode_message(delivery_info, properties, payload)
21
+ [delivery_info, properties, payload]
22
+ end
23
+
24
+ def prefetch_channel(ch, prefetch)
25
+ ch.prefetch(prefetch) if prefetch
26
+ end
27
+
28
+ def current_timestamp
29
+ Time.now.to_i
30
+ end
31
+
32
+ def self.new_exchange(ch, exchange_type, exchange_name, exchange_options)
33
+ Bunny::Exchange.new(ch, exchange_type, exchange_name, exchange_options)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ require 'march_hare'
2
+ require 'forwardable'
3
+
4
+ module Hutch
5
+ module Adapters
6
+ class MarchHareAdapter
7
+ extend Forwardable
8
+
9
+ DEFAULT_VHOST = "/"
10
+
11
+ ConnectionRefused = MarchHare::ConnectionRefused
12
+ PreconditionFailed = MarchHare::PreconditionFailed
13
+
14
+ def_delegators :@connection, :start, :disconnect, :close, :open?
15
+
16
+ def initialize(opts = {})
17
+ @connection = MarchHare.connect(opts)
18
+ end
19
+
20
+ def self.decode_message(delivery_info, payload)
21
+ [delivery_info, delivery_info.properties, payload]
22
+ end
23
+
24
+ def prefetch_channel(ch, prefetch)
25
+ ch.prefetch = prefetch if prefetch
26
+ end
27
+
28
+ def create_channel(n = nil, consumer_pool_size = 1, consumer_pool_abort_on_exception = false)
29
+ @connection.create_channel(n)
30
+ end
31
+
32
+ def current_timestamp
33
+ Time.now
34
+ end
35
+
36
+ def self.new_exchange(ch, exchange_type, exchange_name, exchange_options)
37
+ MarchHare::Exchange.new(ch, exchange_name, exchange_options.merge(type: exchange_type))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,384 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ require 'carrot-top'
4
+ require 'hutch/logging'
5
+ require 'hutch/exceptions'
6
+ require 'hutch/publisher'
7
+
8
+ module Hutch
9
+ class Broker
10
+ include Logging
11
+
12
+ attr_accessor :connection, :channel, :exchange, :api_client
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
33
+ def initialize(config = nil)
34
+ @config = config || Hutch::Config
35
+ end
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
46
+ def connect(options = {})
47
+ @options = options
48
+ set_up_amqp_connection
49
+ if http_api_use_enabled?
50
+ logger.info "HTTP API use is enabled"
51
+ set_up_api_connection
52
+ else
53
+ logger.info "HTTP API use is disabled"
54
+ end
55
+
56
+ if tracing_enabled?
57
+ logger.info "tracing is enabled using #{@config[:tracer]}"
58
+ else
59
+ logger.info "tracing is disabled"
60
+ end
61
+
62
+ if block_given?
63
+ begin
64
+ yield
65
+ ensure
66
+ disconnect
67
+ end
68
+ end
69
+ end
70
+
71
+ def disconnect
72
+ @channel.close if @channel
73
+ @connection.close if @connection
74
+ @channel = nil
75
+ @connection = nil
76
+ @exchange = nil
77
+ @api_client = nil
78
+ end
79
+
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.
84
+ def set_up_amqp_connection
85
+ open_connection!
86
+ open_channel!
87
+ declare_exchange!
88
+ declare_publisher!
89
+ end
90
+
91
+ def open_connection
92
+ logger.info "connecting to rabbitmq (#{sanitized_uri})"
93
+
94
+ connection = Hutch::Adapter.new(connection_params)
95
+
96
+ with_bunny_connection_handler(sanitized_uri) do
97
+ connection.start
98
+ end
99
+
100
+ logger.info "connected to RabbitMQ at #{connection_params[:host]} as #{connection_params[:username]}"
101
+ connection
102
+ end
103
+
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])
112
+ if @config[:publisher_confirms] || @config[:force_publisher_confirms]
113
+ logger.info 'enabling publisher confirms'
114
+ ch.confirm_select
115
+ end
116
+ end
117
+ end
118
+
119
+ def open_channel!
120
+ @channel = open_channel
121
+ end
122
+
123
+ def declare_exchange(name,channel)
124
+ exchange_name = name or @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
+ Adapter.new_exchange(channel, exchange_type, exchange_name, exchange_options)
131
+ end
132
+ end
133
+
134
+ def declare_exchange!
135
+ name = @config[:mq_exchange]
136
+ @exchange = declare_exchange(name,channel)
137
+ end
138
+
139
+ def declare_publisher!
140
+ @publisher = Hutch::Publisher.new(connection, channel, exchange, @config)
141
+ end
142
+
143
+ # Set up the connection to the RabbitMQ management API. Unfortunately, this
144
+ # is necessary to do a few things that are impossible over AMQP. E.g.
145
+ # listing queues and bindings.
146
+ def set_up_api_connection
147
+ logger.info "connecting to rabbitmq HTTP API (#{api_config.sanitized_uri})"
148
+
149
+ with_authentication_error_handler do
150
+ with_connection_error_handler do
151
+ @api_client = CarrotTop.new(host: api_config.host, port: api_config.port,
152
+ user: api_config.username, password: api_config.password,
153
+ ssl: api_config.ssl)
154
+ @api_client.exchanges
155
+ end
156
+ end
157
+ end
158
+
159
+ def http_api_use_enabled?
160
+ op = @options.fetch(:enable_http_api_use, true)
161
+ cf = if @config[:enable_http_api_use].nil?
162
+ true
163
+ else
164
+ @config[:enable_http_api_use]
165
+ end
166
+
167
+ op && cf
168
+ end
169
+
170
+ def tracing_enabled?
171
+ @config[:tracer] && @config[:tracer] != Hutch::Tracers::NullTracer
172
+ end
173
+
174
+ # Create / get a durable queue and apply namespace if it exists.
175
+ def queue(name, options = {})
176
+ with_bunny_precondition_handler('queue') do
177
+ namespace = @config[:namespace].to_s.downcase.gsub(/[^-_:\.\w]/, "")
178
+ queue_name = namespace.present? ? "#{namespace}:#{name}" : name
179
+ channel.queue(queue_name, **options)
180
+ end
181
+ end
182
+
183
+ # Return a mapping of queue names to the routing keys they're bound to.
184
+ def bindings
185
+ results = Hash.new { |hash, key| hash[key] = [] }
186
+
187
+ filtered = api_client.bindings.
188
+ reject { |b| b['destination'] == b['routing_key'] }.
189
+ select { |b| b['source'] == @config[:mq_exchange] && b['vhost'] == @config[:mq_vhost] }
190
+
191
+ filtered.each do |binding|
192
+ results[binding['destination']] << binding['routing_key']
193
+ end
194
+
195
+ results
196
+ end
197
+
198
+ # Find the existing bindings, and unbind any redundant bindings
199
+ def unbind_redundant_bindings(queue, routing_keys)
200
+ return unless http_api_use_enabled?
201
+
202
+ filtered = bindings.select { |dest, keys| dest == queue.name }
203
+ filtered.each do |dest, keys|
204
+ keys.reject { |key| routing_keys.include?(key) }.each do |key|
205
+ logger.debug "removing redundant binding #{queue.name} <--> #{key}"
206
+ queue.unbind(exchange, routing_key: key)
207
+ end
208
+ end
209
+ end
210
+
211
+ # Bind a queue to the broker's exchange on the routing keys provided. Any
212
+ # existing bindings on the queue that aren't present in the array of
213
+ # routing keys will be unbound.
214
+ def bind_queue(queue, routing_keys)
215
+ unbind_redundant_bindings(queue, routing_keys)
216
+
217
+ # Ensure all the desired bindings are present
218
+ routing_keys.each do |routing_key|
219
+ logger.debug "creating binding #{queue.name} <--> #{routing_key}"
220
+ queue.bind(exchange, routing_key: routing_key)
221
+ end
222
+ end
223
+
224
+ def stop
225
+ if defined?(JRUBY_VERSION)
226
+ channel.close
227
+ else
228
+ # Enqueue a failing job that kills the consumer loop
229
+ channel_work_pool.shutdown
230
+ # Give `timeout` seconds to jobs that are still being processed
231
+ channel_work_pool.join(@config[:graceful_exit_timeout])
232
+ # If after `timeout` they are still running, they are killed
233
+ channel_work_pool.kill
234
+ end
235
+ end
236
+
237
+ def requeue(delivery_tag)
238
+ channel.reject(delivery_tag, true)
239
+ end
240
+
241
+ def reject(delivery_tag, requeue=false)
242
+ channel.reject(delivery_tag, requeue)
243
+ end
244
+
245
+ def ack(delivery_tag)
246
+ channel.ack(delivery_tag, false)
247
+ end
248
+
249
+ def nack(delivery_tag)
250
+ channel.nack(delivery_tag, false, false)
251
+ end
252
+
253
+ def publish(*args)
254
+ @publisher.publish(*args)
255
+ end
256
+
257
+ def confirm_select(*args)
258
+ channel.confirm_select(*args)
259
+ end
260
+
261
+ def wait_for_confirms
262
+ channel.wait_for_confirms
263
+ end
264
+
265
+ # @return [Boolean] True if channel is set up to use publisher confirmations.
266
+ def using_publisher_confirmations?
267
+ channel.using_publisher_confirmations?
268
+ end
269
+
270
+ private
271
+
272
+ def api_config
273
+ @api_config ||= OpenStruct.new.tap do |config|
274
+ config.host = @config[:mq_api_host]
275
+ config.port = @config[:mq_api_port]
276
+ config.username = @config[:mq_username]
277
+ config.password = @config[:mq_password]
278
+ config.ssl = @config[:mq_api_ssl]
279
+ config.protocol = config.ssl ? "https://" : "http://"
280
+ config.sanitized_uri = "#{config.protocol}#{config.username}@#{config.host}:#{config.port}/"
281
+ end
282
+ end
283
+
284
+ def connection_params
285
+ parse_uri
286
+
287
+ {}.tap do |params|
288
+ params[:host] = @config[:mq_host]
289
+ params[:port] = @config[:mq_port]
290
+ params[:vhost] = @config[:mq_vhost].presence || Hutch::Adapter::DEFAULT_VHOST
291
+ params[:auth_mechanism] = @config[:mq_auth_mechanism]
292
+ params[:username] = @config[:mq_username]
293
+ params[:password] = @config[:mq_password]
294
+ params[:tls] = @config[:mq_tls]
295
+ params[:tls_key] = @config[:mq_tls_key]
296
+ params[:tls_cert] = @config[:mq_tls_cert]
297
+ params[:verify_peer] = @config[:mq_verify_peer]
298
+ if @config[:mq_tls_ca_certificates]
299
+ params[:tls_ca_certificates] = @config[:mq_tls_ca_certificates]
300
+ end
301
+ params[:heartbeat] = @config[:heartbeat]
302
+ params[:connection_timeout] = @config[:connection_timeout]
303
+ params[:read_timeout] = @config[:read_timeout]
304
+ params[:write_timeout] = @config[:write_timeout]
305
+
306
+
307
+ params[:automatically_recover] = @config[:automatically_recover]
308
+ params[:network_recovery_interval] = @config[:network_recovery_interval]
309
+
310
+ params[:logger] = @config[:client_logger] if @config[:client_logger]
311
+ end
312
+ end
313
+
314
+ def parse_uri
315
+ return if @config[:uri].blank?
316
+
317
+ u = URI.parse(@config[:uri])
318
+
319
+ @config[:mq_tls] = u.scheme == 'amqps'
320
+ @config[:mq_host] = u.host
321
+ @config[:mq_port] = u.port || default_mq_port
322
+ @config[:mq_vhost] = u.path.sub(/^\//, "")
323
+ @config[:mq_username] = u.user
324
+ @config[:mq_password] = u.password
325
+ end
326
+
327
+ def default_mq_port
328
+ @config[:mq_tls] ? DEFAULT_AMQPS_PORT : DEFAULT_AMQP_PORT
329
+ end
330
+
331
+ def sanitized_uri
332
+ p = connection_params
333
+ scheme = p[:tls] ? "amqps" : "amqp"
334
+
335
+ "#{scheme}://#{p[:username]}@#{p[:host]}:#{p[:port]}/#{p[:vhost].sub(/^\//, '')}"
336
+ end
337
+
338
+ def with_authentication_error_handler
339
+ yield
340
+ rescue Net::HTTPServerException => ex
341
+ logger.error "HTTP API connection error: #{ex.message.downcase}"
342
+ if ex.response.code == '401'
343
+ raise AuthenticationError.new('invalid HTTP API credentials')
344
+ else
345
+ raise
346
+ end
347
+ end
348
+
349
+ def with_connection_error_handler
350
+ yield
351
+ rescue Errno::ECONNREFUSED => ex
352
+ logger.error "HTTP API connection error: #{ex.message.downcase}"
353
+ raise ConnectionError.new("couldn't connect to HTTP API at #{api_config.sanitized_uri}")
354
+ end
355
+
356
+ def with_bunny_precondition_handler(item)
357
+ yield
358
+ rescue Hutch::Adapter::PreconditionFailed => ex
359
+ logger.error ex.message
360
+ s = "RabbitMQ responded with 406 Precondition Failed when creating this #{item}. " +
361
+ "Perhaps it is being redeclared with non-matching attributes"
362
+ raise WorkerSetupError.new(s)
363
+ end
364
+
365
+ def with_bunny_connection_handler(uri)
366
+ yield
367
+ rescue Hutch::Adapter::ConnectionRefused => ex
368
+ logger.error "amqp connection error: #{ex.message.downcase}"
369
+ raise ConnectionError.new("couldn't connect to rabbitmq at #{uri}. Check your configuration, network connectivity and RabbitMQ logs.")
370
+ end
371
+
372
+ def channel_work_pool
373
+ channel.work_pool
374
+ end
375
+
376
+ def consumer_pool_size
377
+ @config[:consumer_pool_size]
378
+ end
379
+
380
+ def consumer_pool_abort_on_exception
381
+ @config[:consumer_pool_abort_on_exception]
382
+ end
383
+ end
384
+ end