hutch 0.24.0 → 1.0.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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +18 -13
  3. data/CHANGELOG.md +227 -4
  4. data/Gemfile +3 -3
  5. data/LICENSE +1 -0
  6. data/README.md +94 -94
  7. data/bin/ci/before_build.sh +20 -0
  8. data/bin/ci/install_on_debian.sh +17 -0
  9. data/hutch.gemspec +5 -5
  10. data/lib/hutch.rb +8 -4
  11. data/lib/hutch/broker.rb +37 -10
  12. data/lib/hutch/cli.rb +22 -11
  13. data/lib/hutch/config.rb +12 -0
  14. data/lib/hutch/consumer.rb +32 -2
  15. data/lib/hutch/error_handlers.rb +1 -1
  16. data/lib/hutch/error_handlers/airbrake.rb +20 -2
  17. data/lib/hutch/error_handlers/base.rb +15 -0
  18. data/lib/hutch/error_handlers/honeybadger.rb +28 -14
  19. data/lib/hutch/error_handlers/logger.rb +7 -2
  20. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  21. data/lib/hutch/error_handlers/sentry.rb +9 -2
  22. data/lib/hutch/publisher.rb +1 -1
  23. data/lib/hutch/tracers.rb +0 -1
  24. data/lib/hutch/version.rb +1 -2
  25. data/lib/hutch/waiter.rb +1 -1
  26. data/lib/hutch/worker.rb +30 -1
  27. data/spec/hutch/broker_spec.rb +34 -0
  28. data/spec/hutch/cli_spec.rb +13 -0
  29. data/spec/hutch/consumer_spec.rb +82 -4
  30. data/spec/hutch/error_handlers/airbrake_spec.rb +19 -0
  31. data/spec/hutch/error_handlers/honeybadger_spec.rb +22 -1
  32. data/spec/hutch/error_handlers/logger_spec.rb +11 -0
  33. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  34. data/spec/hutch/error_handlers/sentry_spec.rb +15 -0
  35. data/spec/hutch/waiter_spec.rb +2 -2
  36. data/spec/hutch/worker_spec.rb +1 -1
  37. metadata +22 -17
  38. data/lib/hutch/error_handlers/opbeat.rb +0 -24
  39. data/lib/hutch/tracers/opbeat.rb +0 -37
  40. data/spec/hutch/error_handlers/opbeat_spec.rb +0 -22
  41. data/spec/tracers/opbeat_spec.rb +0 -44
@@ -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,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
@@ -42,7 +42,7 @@ module Hutch
42
42
  private
43
43
 
44
44
  def log_publication(serializer, payload, routing_key)
45
- logger.info {
45
+ logger.debug {
46
46
  spec =
47
47
  if serializer.binary?
48
48
  "#{payload.bytesize} bytes message"
data/lib/hutch/tracers.rb CHANGED
@@ -2,6 +2,5 @@ module Hutch
2
2
  module Tracers
3
3
  autoload :NullTracer, 'hutch/tracers/null_tracer'
4
4
  autoload :NewRelic, 'hutch/tracers/newrelic'
5
- autoload :Opbeat, 'hutch/tracers/opbeat'
6
5
  end
7
6
  end
data/lib/hutch/version.rb CHANGED
@@ -1,4 +1,3 @@
1
1
  module Hutch
2
- VERSION = '0.24.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
4
-
data/lib/hutch/waiter.rb CHANGED
@@ -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
data/lib/hutch/worker.rb CHANGED
@@ -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
+ !consumers_to_load.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
@@ -28,6 +28,32 @@ describe Hutch::Consumer do
28
28
  ComplexConsumer
29
29
  end
30
30
 
31
+ let(:consumer_using_quorum_queue) do
32
+ unless defined? ConsumerUsingQuorumQueue
33
+ class ConsumerUsingQuorumQueue
34
+ include Hutch::Consumer
35
+ consume 'hutch.test1'
36
+ arguments foo: :bar
37
+
38
+ quorum_queue
39
+ end
40
+ end
41
+ ConsumerUsingQuorumQueue
42
+ end
43
+
44
+ let(:consumer_using_classic_queue) do
45
+ unless defined? ConsumerUsingLazyQueue
46
+ class ConsumerUsingLazyQueue
47
+ include Hutch::Consumer
48
+ consume 'hutch.test1'
49
+ arguments foo: :bar
50
+ lazy_queue
51
+ classic_queue
52
+ end
53
+ end
54
+ ConsumerUsingLazyQueue
55
+ end
56
+
31
57
  describe 'module inclusion' do
32
58
  it 'registers the class as a consumer' do
33
59
  expect(Hutch).to receive(:register_consumer) do |klass|
@@ -71,6 +97,43 @@ describe Hutch::Consumer do
71
97
  end
72
98
  end
73
99
 
100
+ describe 'default queue mode' do
101
+ it 'does not specify any mode by default' do
102
+ expect(simple_consumer.queue_mode).to eq(nil)
103
+ expect(simple_consumer.queue_type).to eq(nil)
104
+ end
105
+ end
106
+
107
+ describe '.lazy_queue' do
108
+ context 'when queue mode has been set explicitly to lazy' do
109
+ it 'sets queue mode to lazy' do
110
+ expect(consumer_using_classic_queue.queue_mode).to eq('lazy')
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '.classic_queue' do
116
+ context 'when queue type has been set explicitly to classic' do
117
+ it 'sets queue type to classic' do
118
+ expect(consumer_using_classic_queue.queue_type).to eq('classic')
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '.quorum_queue' do
124
+ context 'when queue type has been set explicitly to quorum' do
125
+ it 'sets queue type to quorum' do
126
+ expect(consumer_using_quorum_queue.queue_type).to eq('quorum')
127
+ end
128
+
129
+ it 'accepts initial group size as an option' do
130
+ consumer = simple_consumer
131
+ expect { consumer.quorum_queue(initial_group_size: 3) }
132
+ .to change { consumer.initial_group_size }.to(3)
133
+ end
134
+ end
135
+ end
136
+
74
137
  describe '.arguments' do
75
138
  let(:args) { { foo: :bar} }
76
139
 
@@ -82,15 +145,30 @@ describe Hutch::Consumer do
82
145
  end
83
146
 
84
147
  describe '.get_arguments' do
85
-
86
148
  context 'when defined' do
87
- it { expect(complex_consumer.get_arguments).to eq(foo: :bar) }
149
+ it { expect(complex_consumer.get_arguments).to include(foo: :bar) }
88
150
  end
89
151
 
90
- context 'when not defined' do
91
- it { expect(simple_consumer.get_arguments).to eq({}) }
152
+ context 'when queue is lazy' do
153
+ it 'has the x-queue-mode argument set to lazy' do
154
+ expect(consumer_using_classic_queue.get_arguments['x-queue-mode'])
155
+ .to eq('lazy')
156
+ end
92
157
  end
93
158
 
159
+ context "when queue's type is quorum" do
160
+ let(:arguments) { consumer_using_quorum_queue.get_arguments }
161
+ it 'has the x-queue-type argument set to quorum' do
162
+ expect(arguments['x-queue-type']).to eq('quorum')
163
+ expect(arguments).to_not have_key('x-quorum-initial-group-size')
164
+ end
165
+
166
+ it 'has the x-quorum-initial-group-size argument set to quorum' do
167
+ consumer_using_quorum_queue.quorum_queue(initial_group_size: 5)
168
+ expect(arguments['x-queue-type']).to eq('quorum')
169
+ expect(arguments['x-quorum-initial-group-size']).to eq(5)
170
+ end
171
+ end
94
172
  end
95
173
 
96
174
  describe '.get_queue_name' 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
@@ -30,8 +30,29 @@ describe Hutch::ErrorHandlers::Honeybadger do
30
30
  :payload => payload
31
31
  }
32
32
  }
33
- expect(::Honeybadger).to receive(:notify_or_ignore).with(message)
33
+ expect(error_handler).to receive(:notify_honeybadger).with(message)
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
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Rollbar do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Rollbar.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Rollbar" do
16
+ message_id = "1"
17
+ properties = OpenStruct.new(message_id: message_id)
18
+ payload = "{}"
19
+ consumer = double
20
+ ex = error
21
+ message = {
22
+ payload: payload,
23
+ consumer: consumer
24
+ }
25
+ expect(::Rollbar).to receive(:error).with(ex, message)
26
+ error_handler.handle(properties, payload, consumer, ex)
27
+ end
28
+ end
29
+
30
+ describe '#handle_setup_exception' do
31
+ let(:error) do
32
+ begin
33
+ raise "Stuff went wrong"
34
+ rescue RuntimeError => err
35
+ err
36
+ end
37
+ end
38
+
39
+ it "logs the error to Rollbar" do
40
+ ex = error
41
+ expect(::Rollbar).to receive(:error).with(ex)
42
+ error_handler.handle_setup_exception(ex)
43
+ end
44
+ end
45
+ end