hutch 0.24.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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