hutch 0.25.0.pre.rc1 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 720fbfbc59995920a3fa51951df1e692af84a36e
4
- data.tar.gz: 30fd95c8766d99b42c6a8a45cf3575dce1219e0b
3
+ metadata.gz: 6f166e0937fb1f468d18247afad90ff52263de57
4
+ data.tar.gz: 97b3c87cd087984409fda36f620c180a71df03d8
5
5
  SHA512:
6
- metadata.gz: 99b267d79bc4497f353cb96735058c770d42545ed76eb4f53033a1518fc809e11fd68dd6e14ab1a17ccf96bdc8a816b9aeba1fd4dee5d03231e701a0d98be3c8
7
- data.tar.gz: c3a36d75316070a6bcf163d7ac3e2c0e39e179f34f8e8db2d9ae5dc56fd9b666d931e734ac7352cba143c313835eb28d4659d0fe0569450f1c1ab32e02068ffd
6
+ metadata.gz: 549463d55e4bfa5ee9b112c42a06c98999c25ab14a8562ef41adc75888f8eba30a42ebe39097ec7366939ad0ef44e38d6ffab8103f6f739f7e2aad68e955488c
7
+ data.tar.gz: 55972b247cb63587772adc3f12d40e852d6332c9991e08ddba7c388a4ab711851d81e3399b8d0c7407c2a16584f20f8c59aaede191f9eb41cd1d46208a0acb29
@@ -1,16 +1,13 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  before_install:
4
- - gem update --system
5
4
  - gem install bundler
6
5
  matrix:
7
6
  include:
8
- - rvm: 2.4.1
9
- - rvm: 2.3.4
10
- - rvm: 2.2.7
11
- - rvm: 2.1
12
- - rvm: 2.0
13
- - rvm: jruby-9.1.12.0
7
+ - rvm: 2.4.2
8
+ - rvm: 2.3.5
9
+ - rvm: 2.2.8
10
+ - rvm: jruby-9.1.15.0
14
11
  jdk: oraclejdk8
15
12
  env:
16
13
  - JRUBY_OPTS='--debug'
data/Gemfile CHANGED
@@ -23,7 +23,7 @@ group :development, :test do
23
23
  gem "honeybadger"
24
24
  gem "coveralls", "~> 0.8.15", require: false
25
25
  gem "newrelic_rpm"
26
- gem "airbrake", "~> 5.0"
26
+ gem "airbrake", "~> 7.0"
27
27
  gem "opbeat", "~> 3.0.9"
28
28
  end
29
29
 
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.add_runtime_dependency 'march_hare', '>= 3.0.0'
7
7
  else
8
8
  gem.platform = Gem::Platform::RUBY
9
- gem.add_runtime_dependency 'bunny', '>= 2.7.0'
9
+ gem.add_runtime_dependency 'bunny', '~> 2.9.0'
10
10
  end
11
11
  gem.add_runtime_dependency 'carrot-top', '~> 0.0.7'
12
12
  gem.add_runtime_dependency 'multi_json', '~> 1.12'
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.description = 'Hutch is a Ruby library for enabling asynchronous ' \
18
18
  'inter-service communication using RabbitMQ.'
19
19
  gem.version = Hutch::VERSION.dup
20
- gem.required_ruby_version = '>= 2.0'
20
+ gem.required_ruby_version = '>= 2.2'
21
21
  gem.authors = ['Harry Marr']
22
22
  gem.email = ['developers@gocardless.com']
23
23
  gem.homepage = 'https://github.com/gocardless/hutch'
@@ -11,6 +11,24 @@ module Hutch
11
11
 
12
12
  attr_accessor :connection, :channel, :exchange, :api_client
13
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
+
14
32
  # @param config [nil,Hash] Configuration override
15
33
  def initialize(config = nil)
16
34
  @config = config || Hutch::Config
@@ -292,13 +310,18 @@ module Hutch
292
310
 
293
311
  u = URI.parse(@config[:uri])
294
312
 
313
+ @config[:mq_tls] = u.scheme == 'amqps'
295
314
  @config[:mq_host] = u.host
296
- @config[:mq_port] = u.port
315
+ @config[:mq_port] = u.port || default_mq_port
297
316
  @config[:mq_vhost] = u.path.sub(/^\//, "")
298
317
  @config[:mq_username] = u.user
299
318
  @config[:mq_password] = u.password
300
319
  end
301
320
 
321
+ def default_mq_port
322
+ @config[:mq_tls] ? DEFAULT_AMQPS_PORT : DEFAULT_AMQP_PORT
323
+ end
324
+
302
325
  def sanitized_uri
303
326
  p = connection_params
304
327
  scheme = p[:tls] ? "amqps" : "amqp"
@@ -90,6 +90,9 @@ module Hutch
90
90
  @worker.run
91
91
  :success
92
92
  rescue ConnectionError, AuthenticationError, WorkerSetupError => ex
93
+ Hutch::Config[:error_handlers].each do |backend|
94
+ backend.handle_setup_exception(ex)
95
+ end
93
96
  logger.fatal ex.message
94
97
  :error
95
98
  end
@@ -189,6 +192,10 @@ module Hutch
189
192
  Hutch::Config.pidfile = pidfile
190
193
  end
191
194
 
195
+ opts.on('--only-group GROUP', 'Load only consumers in this group') do |group|
196
+ Hutch::Config.group = group
197
+ end
198
+
192
199
  opts.on('--version', 'Print the version and exit') do
193
200
  puts "hutch v#{VERSION}"
194
201
  exit 0
@@ -136,6 +136,8 @@ module Hutch
136
136
  # Prefix displayed on the consumers tags.
137
137
  string_setting :consumer_tag_prefix, 'hutch'
138
138
 
139
+ string_setting :group, ''
140
+
139
141
  # Set of all setting keys
140
142
  ALL_KEYS = @boolean_keys + @number_keys + @string_keys
141
143
 
@@ -167,6 +169,7 @@ module Hutch
167
169
  # that will fall back to "nack unconditionally"
168
170
  error_acknowledgements: [],
169
171
  setup_procs: [],
172
+ consumer_groups: {},
170
173
  tracer: Hutch::Tracers::NullTracer,
171
174
  namespace: nil,
172
175
  pidfile: nil,
@@ -1,10 +1,10 @@
1
1
  require 'hutch/logging'
2
2
  require 'airbrake'
3
+ require 'hutch/error_handlers/base'
3
4
 
4
5
  module Hutch
5
6
  module ErrorHandlers
6
- class Airbrake
7
- include Logging
7
+ class Airbrake < Base
8
8
 
9
9
  def handle(properties, payload, consumer, ex)
10
10
  message_id = properties.message_id
@@ -31,6 +31,24 @@ module Hutch
31
31
  })
32
32
  end
33
33
  end
34
+
35
+ def handle_setup_exception(ex)
36
+ logger.error "Logging setup exception to Airbrake"
37
+ logger.error "#{ex.class} - #{ex.message}"
38
+
39
+ if ::Airbrake.respond_to?(:notify_or_ignore)
40
+ ::Airbrake.notify_or_ignore(ex, {
41
+ error_class: ex.class.name,
42
+ error_message: "#{ ex.class.name }: #{ ex.message }",
43
+ backtrace: ex.backtrace,
44
+ cgi_data: ENV.to_hash,
45
+ })
46
+ else
47
+ ::Airbrake.notify(ex, {
48
+ cgi_data: ENV.to_hash,
49
+ })
50
+ end
51
+ end
34
52
  end
35
53
  end
36
54
  end
@@ -0,0 +1,15 @@
1
+ module Hutch
2
+ module ErrorHandlers
3
+ class Base
4
+ include Logging
5
+
6
+ def handle(properties, payload, consumer, ex)
7
+ raise NotImplementedError.new
8
+ end
9
+
10
+ def handle_setup_exception(ex)
11
+ raise NotImplementedError.new
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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.merge(force: true))
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
@@ -1,10 +1,10 @@
1
1
  require 'hutch/logging'
2
2
  require 'opbeat'
3
+ require 'hutch/error_handlers/base'
3
4
 
4
5
  module Hutch
5
6
  module ErrorHandlers
6
- class Opbeat
7
- include Logging
7
+ class Opbeat < Base
8
8
 
9
9
  def initialize
10
10
  unless ::Opbeat.respond_to?(:report)
@@ -19,6 +19,12 @@ module Hutch
19
19
  logger.error "#{prefix} #{ex.class} - #{ex.message}"
20
20
  ::Opbeat.report(ex, extra: { payload: payload })
21
21
  end
22
+
23
+ def handle_setup_exception(ex)
24
+ logger.error "Logging setup exception to Opbeat"
25
+ logger.error "#{ex.class} - #{ex.message}"
26
+ ::Opbeat.report(ex)
27
+ end
22
28
  end
23
29
  end
24
30
  end
@@ -1,10 +1,10 @@
1
1
  require 'hutch/logging'
2
2
  require 'raven'
3
+ require 'hutch/error_handlers/base'
3
4
 
4
5
  module Hutch
5
6
  module ErrorHandlers
6
- class Sentry
7
- include Logging
7
+ class Sentry < Base
8
8
 
9
9
  def initialize
10
10
  unless Raven.respond_to?(:capture_exception)
@@ -19,6 +19,13 @@ module Hutch
19
19
  logger.error "#{prefix} #{ex.class} - #{ex.message}"
20
20
  Raven.capture_exception(ex, extra: { payload: payload })
21
21
  end
22
+
23
+ def handle_setup_exception(ex)
24
+ logger.error "Logging setup exception to Sentry"
25
+ logger.error "#{ex.class} - #{ex.message}"
26
+ Raven.capture_exception(ex)
27
+ end
28
+
22
29
  end
23
30
  end
24
31
  end
@@ -1,4 +1,4 @@
1
1
  module Hutch
2
- VERSION = '0.25.0-rc1'.freeze
2
+ VERSION = '0.25.0'.freeze
3
3
  end
4
4
 
@@ -50,7 +50,7 @@ module Hutch
50
50
  end
51
51
  end
52
52
 
53
- # @raises ContinueProcessingSignals
53
+ # @raise ContinueProcessingSignals
54
54
  def handle_user_signal(sig)
55
55
  case sig
56
56
  when 'USR2' then log_thread_backtraces
@@ -36,12 +36,17 @@ module Hutch
36
36
  # Set up the queues for each of the worker's consumers.
37
37
  def setup_queues
38
38
  logger.info 'setting up queues'
39
- @consumers.each { |consumer| setup_queue(consumer) }
39
+ vetted = @consumers.reject { |c| group_configured? && group_restricted?(c) }
40
+ vetted.each do |c|
41
+ setup_queue(c)
42
+ end
40
43
  end
41
44
 
42
45
  # Bind a consumer's routing keys to its queue, and set up a subscription to
43
46
  # receive messages sent to the queue.
44
47
  def setup_queue(consumer)
48
+ logger.info "setting up queue: #{consumer.get_queue_name}"
49
+
45
50
  queue = @broker.queue(consumer.get_queue_name, consumer.get_arguments)
46
51
  @broker.bind_queue(queue, consumer.routing_keys)
47
52
 
@@ -103,6 +108,30 @@ module Hutch
103
108
 
104
109
  private
105
110
 
111
+ def group_configured?
112
+ if group.present? && consumer_groups.blank?
113
+ logger.info 'Consumer groups are blank'
114
+ end
115
+ group.present?
116
+ end
117
+
118
+ def group_restricted?(consumer)
119
+ consumers_to_load = consumer_groups[group]
120
+ if consumers_to_load
121
+ !allowed_consumers.include?(consumer.name)
122
+ else
123
+ true
124
+ end
125
+ end
126
+
127
+ def group
128
+ Hutch::Config[:group]
129
+ end
130
+
131
+ def consumer_groups
132
+ Hutch::Config[:consumer_groups]
133
+ end
134
+
106
135
  attr_accessor :setup_procs
107
136
 
108
137
  def unique_consumer_tag
@@ -92,6 +92,40 @@ 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://guest:guest@127.0.0.1:5672/' }
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://guest:guest@127.0.0.1/' }
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://guest:guest@127.0.0.1/' }
119
+
120
+ it 'utilises TLS' do
121
+ expect(Hutch::Adapter).to receive(:new).with(
122
+ hash_including(tls: true, port: 5671)
123
+ ).and_return(instance_double('Hutch::Adapter', start: nil))
124
+
125
+ broker.open_connection
126
+ end
127
+ end
128
+ end
95
129
  end
96
130
 
97
131
  describe '#open_connection!' do
@@ -4,6 +4,19 @@ require 'tempfile'
4
4
  describe Hutch::CLI do
5
5
  let(:cli) { Hutch::CLI.new }
6
6
 
7
+ describe "#start_work_loop" do
8
+ context "connection error during setup" do
9
+ let(:error) { Hutch::ConnectionError.new }
10
+ it "gets reported using error handlers" do
11
+ allow(Hutch).to receive(:connect).and_raise(error)
12
+ Hutch::Config[:error_handlers].each do |backend|
13
+ expect(backend).to receive(:handle_setup_exception).with(error)
14
+ end
15
+ cli.start_work_loop
16
+ end
17
+ end
18
+ end
19
+
7
20
  describe "#parse_options" do
8
21
  context "--config" do
9
22
  context "when the config file does not exist" do
@@ -27,4 +27,23 @@ describe Hutch::ErrorHandlers::Airbrake do
27
27
  error_handler.handle(properties, payload, consumer, ex)
28
28
  end
29
29
  end
30
+
31
+ describe '#handle_setup_exception' do
32
+ let(:error) do
33
+ begin
34
+ raise "Stuff went wrong"
35
+ rescue RuntimeError => err
36
+ err
37
+ end
38
+ end
39
+
40
+ it "logs the error to Airbrake" do
41
+ ex = error
42
+ message = {
43
+ cgi_data: ENV.to_hash,
44
+ }
45
+ expect(::Airbrake).to receive(:notify).with(ex, message)
46
+ error_handler.handle_setup_exception(ex)
47
+ end
48
+ end
30
49
  end
@@ -34,4 +34,25 @@ describe Hutch::ErrorHandlers::Honeybadger do
34
34
  error_handler.handle(properties, payload, consumer, ex)
35
35
  end
36
36
  end
37
+
38
+ describe '#handle_setup_exception' do
39
+ let(:error) do
40
+ begin
41
+ raise "Stuff went wrong during setup"
42
+ rescue RuntimeError => err
43
+ err
44
+ end
45
+ end
46
+
47
+ it "logs the error to Honeybadger" do
48
+ ex = error
49
+ message = {
50
+ :error_class => ex.class.name,
51
+ :error_message => "#{ ex.class.name }: #{ ex.message }",
52
+ :backtrace => ex.backtrace,
53
+ }
54
+ expect(error_handler).to receive(:notify_honeybadger).with(message)
55
+ error_handler.handle_setup_exception(ex)
56
+ end
57
+ end
37
58
  end
@@ -14,4 +14,15 @@ describe Hutch::ErrorHandlers::Logger do
14
14
  error_handler.handle(properties, payload, double, error)
15
15
  end
16
16
  end
17
+
18
+ describe '#handle_setup_exception' do
19
+ let(:error) { double(message: "Stuff went wrong during setup",
20
+ class: "RuntimeError",
21
+ backtrace: ["line 1", "line 2"]) }
22
+
23
+ it "logs two separate lines" do
24
+ expect(Hutch::Logging.logger).to receive(:error).exactly(2).times
25
+ error_handler.handle_setup_exception(error)
26
+ end
27
+ end
17
28
  end
@@ -19,4 +19,19 @@ describe Hutch::ErrorHandlers::Opbeat do
19
19
  error_handler.handle(properties, payload, double, error)
20
20
  end
21
21
  end
22
+
23
+ describe '#handle_setup_exception' do
24
+ let(:error) do
25
+ begin
26
+ raise "Stuff went wrong during setup"
27
+ rescue RuntimeError => err
28
+ err
29
+ end
30
+ end
31
+
32
+ it "logs the error to Opbeat" do
33
+ expect(Opbeat).to receive(:report).with(error)
34
+ error_handler.handle_setup_exception(error)
35
+ end
36
+ end
22
37
  end
@@ -19,4 +19,19 @@ describe Hutch::ErrorHandlers::Sentry do
19
19
  error_handler.handle(properties, payload, double, error)
20
20
  end
21
21
  end
22
+
23
+ describe '#handle_setup_exception' do
24
+ let(:error) do
25
+ begin
26
+ raise "Stuff went wrong during setup"
27
+ rescue RuntimeError => err
28
+ err
29
+ end
30
+ end
31
+
32
+ it "logs the error to Sentry" do
33
+ expect(Raven).to receive(:capture_exception).with(error)
34
+ error_handler.handle_setup_exception(error)
35
+ end
36
+ end
22
37
  end
@@ -97,7 +97,7 @@ describe Hutch::Worker do
97
97
  it 'requeues the message' do
98
98
  allow(consumer_instance).to receive(:process).and_raise('failed')
99
99
  requeuer = double
100
- allow(requeuer).to receive(:handle).ordered { |delivery_info, properties, broker, e|
100
+ allow(requeuer).to receive(:handle) { |delivery_info, properties, broker, e|
101
101
  broker.requeue delivery_info.delivery_tag
102
102
  true
103
103
  }
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hutch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0.pre.rc1
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harry Marr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-27 00:00:00.000000000 Z
11
+ date: 2018-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.7.0
19
+ version: 2.9.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.7.0
26
+ version: 2.9.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: carrot-top
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +107,7 @@ files:
107
107
  - lib/hutch/consumer.rb
108
108
  - lib/hutch/error_handlers.rb
109
109
  - lib/hutch/error_handlers/airbrake.rb
110
+ - lib/hutch/error_handlers/base.rb
110
111
  - lib/hutch/error_handlers/honeybadger.rb
111
112
  - lib/hutch/error_handlers/logger.rb
112
113
  - lib/hutch/error_handlers/opbeat.rb
@@ -164,15 +165,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
165
  requirements:
165
166
  - - ">="
166
167
  - !ruby/object:Gem::Version
167
- version: '2.0'
168
+ version: '2.2'
168
169
  required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  requirements:
170
- - - ">"
171
+ - - ">="
171
172
  - !ruby/object:Gem::Version
172
- version: 1.3.1
173
+ version: '0'
173
174
  requirements: []
174
175
  rubyforge_project:
175
- rubygems_version: 2.5.2
176
+ rubygems_version: 2.6.11
176
177
  signing_key:
177
178
  specification_version: 4
178
179
  summary: Easy inter-service communication using RabbitMQ.