sanger_warren 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +39 -0
- data/.rubocop.yml +11 -5
- data/.yardopts +3 -0
- data/CHANGELOG.md +34 -1
- data/Gemfile +6 -1
- data/Gemfile.lock +71 -39
- data/README.md +133 -43
- data/bin/console +3 -6
- data/bin/warren +6 -0
- data/lefthook.yml +53 -0
- data/lib/sanger_warren.rb +8 -0
- data/lib/warren.rb +49 -4
- data/lib/warren/app.rb +9 -0
- data/lib/warren/app/cli.rb +35 -0
- data/lib/warren/app/config.rb +110 -0
- data/lib/warren/app/consumer.rb +65 -0
- data/lib/warren/app/consumer_add.rb +131 -0
- data/lib/warren/app/consumer_start.rb +40 -0
- data/lib/warren/app/exchange_config.rb +151 -0
- data/lib/warren/app/templates/subscriber.tt +32 -0
- data/lib/warren/callback.rb +2 -7
- data/lib/warren/callback/broadcast_with_warren.rb +1 -1
- data/lib/warren/client.rb +111 -0
- data/lib/warren/config/consumers.rb +123 -0
- data/lib/warren/delay_exchange.rb +85 -0
- data/lib/warren/den.rb +93 -0
- data/lib/warren/exceptions.rb +15 -0
- data/lib/warren/fox.rb +165 -0
- data/lib/warren/framework_adaptor/rails_adaptor.rb +135 -0
- data/lib/warren/handler.rb +16 -0
- data/lib/warren/handler/base.rb +20 -0
- data/lib/warren/handler/broadcast.rb +54 -18
- data/lib/warren/handler/log.rb +50 -10
- data/lib/warren/handler/test.rb +101 -14
- data/lib/warren/helpers/state_machine.rb +55 -0
- data/lib/warren/log_tagger.rb +58 -0
- data/lib/warren/message.rb +7 -5
- data/lib/warren/message/full.rb +20 -0
- data/lib/warren/message/short.rb +49 -4
- data/lib/warren/message/simple.rb +15 -0
- data/lib/warren/railtie.rb +12 -0
- data/lib/warren/subscriber/base.rb +151 -0
- data/lib/warren/subscription.rb +78 -0
- data/lib/warren/version.rb +2 -1
- data/sanger-warren.gemspec +5 -4
- metadata +49 -6
- data/.travis.yml +0 -6
data/lib/warren/handler.rb
CHANGED
@@ -7,6 +7,22 @@ module Warren
|
|
7
7
|
# A {Warren::Handler} provides an interface for sending messages to either
|
8
8
|
# a message queue, a log, or an internal store for testing purposes.
|
9
9
|
module Handler
|
10
|
+
#
|
11
|
+
# Generates a template for routing keys for the given prefix, or a template
|
12
|
+
# that returns the provided routing key if no prefix is supplied.
|
13
|
+
#
|
14
|
+
# @example With a prefix
|
15
|
+
# template = Warren::Handler.routing_key_template('example') # => 'example.%s'
|
16
|
+
# format(template, 'routing.key') #=> 'example.routing.key'
|
17
|
+
#
|
18
|
+
# @example Without a prefix
|
19
|
+
# template = Warren::Handler.routing_key_template(nil) # => '%s'
|
20
|
+
# format(template, 'routing.key') #=> 'routing.key'
|
21
|
+
#
|
22
|
+
# @param prefix [String, nil] The prefix to use in the template
|
23
|
+
#
|
24
|
+
# @return [String] A template for generating routing keys
|
25
|
+
#
|
10
26
|
def self.routing_key_template(prefix)
|
11
27
|
prefix ? "#{prefix}.%s" : '%s'
|
12
28
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warren
|
4
|
+
module Handler
|
5
|
+
# Base class
|
6
|
+
class Base
|
7
|
+
#
|
8
|
+
# Provide API compatibility with the RabbitMQ versions
|
9
|
+
# Do nothing in this case
|
10
|
+
#
|
11
|
+
def connect; end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Provide API compatibility with the RabbitMQ versions
|
15
|
+
# Do nothing in this case
|
16
|
+
#
|
17
|
+
def disconnect; end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bunny'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'connection_pool'
|
6
|
+
require_relative 'base'
|
4
7
|
|
5
8
|
module Warren
|
6
9
|
module Handler
|
@@ -8,36 +11,60 @@ module Warren
|
|
8
11
|
# Class Warren::Broadcast provides a connection pool of
|
9
12
|
# threadsafe RabbitMQ channels for broadcasting messages
|
10
13
|
#
|
11
|
-
class Broadcast
|
12
|
-
# Wraps a
|
14
|
+
class Broadcast < Warren::Handler::Base
|
15
|
+
# Wraps a Bunny::Channel
|
16
|
+
# @see https://rubydoc.info/gems/bunny/Bunny/Channel
|
13
17
|
class Channel
|
14
|
-
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
attr_reader :routing_key_prefix
|
21
|
+
|
22
|
+
def_delegators :@bun_channel, :close, :exchange, :queue, :prefetch, :ack, :nack
|
23
|
+
|
24
|
+
def initialize(bun_channel, routing_key_prefix:, exchange: nil)
|
15
25
|
@bun_channel = bun_channel
|
16
26
|
@exchange_name = exchange
|
17
|
-
@
|
27
|
+
@routing_key_prefix = routing_key_prefix
|
28
|
+
@routing_key_template = Handler.routing_key_template(routing_key_prefix)
|
18
29
|
end
|
19
30
|
|
31
|
+
# Publishes `message` to the configured exchange
|
32
|
+
#
|
33
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
34
|
+
# @see Warren::Message::Full
|
35
|
+
#
|
36
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
37
|
+
#
|
20
38
|
def <<(message)
|
21
|
-
|
22
|
-
self
|
39
|
+
publish(message)
|
23
40
|
end
|
24
41
|
|
25
|
-
|
26
|
-
|
42
|
+
# Publishes `message` to `exchange` (Defaults to configured exchange)
|
43
|
+
#
|
44
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
45
|
+
# @see Warren::Message::Full
|
46
|
+
# @param exchange [Bunny::Exchange] The exchange to publish to
|
47
|
+
#
|
48
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
49
|
+
#
|
50
|
+
def publish(message, exchange: configured_exchange)
|
51
|
+
exchange.publish(message.payload, routing_key: key_for(message), headers: message.headers)
|
52
|
+
self
|
27
53
|
end
|
28
54
|
|
29
55
|
private
|
30
56
|
|
31
|
-
def
|
57
|
+
def configured_exchange
|
32
58
|
raise StandardError, 'No exchange configured' if @exchange_name.nil?
|
33
59
|
|
34
|
-
@
|
60
|
+
@configured_exchange ||= exchange(@exchange_name, auto_delete: false, durable: true, type: :topic)
|
35
61
|
end
|
36
62
|
|
37
63
|
def key_for(message)
|
38
64
|
@routing_key_template % message.routing_key
|
39
65
|
end
|
40
66
|
end
|
67
|
+
|
41
68
|
#
|
42
69
|
# Creates a warren but does not connect.
|
43
70
|
#
|
@@ -47,10 +74,11 @@ module Warren
|
|
47
74
|
# @param [String,nil] routing_key_prefix The prefix to pass before the routing key.
|
48
75
|
# Can be used to ensure environments remain distinct.
|
49
76
|
def initialize(exchange:, routing_key_prefix:, server: {}, pool_size: 14)
|
77
|
+
super()
|
50
78
|
@server = server
|
51
79
|
@exchange_name = exchange
|
52
80
|
@pool_size = pool_size
|
53
|
-
@
|
81
|
+
@routing_key_prefix = routing_key_prefix
|
54
82
|
end
|
55
83
|
|
56
84
|
#
|
@@ -74,12 +102,12 @@ module Warren
|
|
74
102
|
end
|
75
103
|
|
76
104
|
#
|
77
|
-
# Yields an
|
78
|
-
#
|
105
|
+
# Yields an {Warren::Handler::Broadcast::Channel} which gets returned to the pool on block closure
|
79
106
|
#
|
80
107
|
# @return [void]
|
81
108
|
#
|
82
|
-
# @
|
109
|
+
# @yieldparam [Warren::Handler::Broadcast::Channel] A rabbitMQ channel that sends messages to the configured
|
110
|
+
# exchange
|
83
111
|
def with_channel(&block)
|
84
112
|
connection_pool.with(&block)
|
85
113
|
end
|
@@ -90,7 +118,7 @@ module Warren
|
|
90
118
|
#
|
91
119
|
# @param [Warren::Message] message The message to broadcast. Must respond to #routing_key and #payload
|
92
120
|
#
|
93
|
-
# @return [Warren::Broadcast] Returns itself to allow chaining. But you're
|
121
|
+
# @return [Warren::Handler::Broadcast] Returns itself to allow chaining. But you're
|
94
122
|
# probably better off using #with_channel
|
95
123
|
# in that case
|
96
124
|
#
|
@@ -99,16 +127,24 @@ module Warren
|
|
99
127
|
self
|
100
128
|
end
|
101
129
|
|
130
|
+
def new_channel(worker_count: 1)
|
131
|
+
Channel.new(session.create_channel(nil, worker_count), exchange: @exchange_name,
|
132
|
+
routing_key_prefix: @routing_key_prefix)
|
133
|
+
end
|
134
|
+
|
102
135
|
private
|
103
136
|
|
137
|
+
def server_connection
|
138
|
+
ENV.fetch('WARREN_CONNECTION_URI', @server)
|
139
|
+
end
|
140
|
+
|
104
141
|
def session
|
105
|
-
@session ||= Bunny.new(
|
142
|
+
@session ||= Bunny.new(server_connection)
|
106
143
|
end
|
107
144
|
|
108
145
|
def connection_pool
|
109
146
|
@connection_pool ||= start_session && ConnectionPool.new(size: @pool_size, timeout: 5) do
|
110
|
-
|
111
|
-
routing_key_template: @routing_key_template)
|
147
|
+
new_channel
|
112
148
|
end
|
113
149
|
end
|
114
150
|
|
data/lib/warren/handler/log.rb
CHANGED
@@ -1,23 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base'
|
4
|
+
|
3
5
|
module Warren
|
4
6
|
module Handler
|
5
7
|
# Class Warren::Log provides a dummy RabbitMQ
|
6
8
|
# connection pool for use during development
|
7
|
-
class Log
|
8
|
-
# Mimics a {
|
9
|
+
class Log < Warren::Handler::Base
|
10
|
+
# Mimics a {Broadcast::Channel} but instead passes out to a logger
|
9
11
|
class Channel
|
10
12
|
def initialize(logger, routing_key_template: '%s')
|
11
13
|
@logger = logger
|
12
14
|
@routing_key_template = routing_key_template
|
13
15
|
end
|
14
16
|
|
17
|
+
# Logs `message` to the configured logger
|
18
|
+
#
|
19
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
20
|
+
# @see Warren::Message::Full
|
21
|
+
#
|
22
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
23
|
+
#
|
15
24
|
def <<(message)
|
16
25
|
@logger.info "Published: #{key_for(message)}"
|
17
26
|
@logger.debug "Payload: #{message.payload}"
|
18
27
|
self
|
19
28
|
end
|
20
29
|
|
30
|
+
def exchange(name, options)
|
31
|
+
@logger.debug "Declared exchange: #{name}, #{options.inspect}"
|
32
|
+
Exchange.new(name, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def queue(name, options)
|
36
|
+
@logger.debug "Declared queue: #{name}, #{options.inspect}"
|
37
|
+
Queue.new(@logger, name)
|
38
|
+
end
|
39
|
+
|
40
|
+
# NOOP - Provided for API compatibility
|
41
|
+
def prefetch(number); end
|
42
|
+
|
21
43
|
private
|
22
44
|
|
23
45
|
def key_for(message)
|
@@ -25,20 +47,38 @@ module Warren
|
|
25
47
|
end
|
26
48
|
end
|
27
49
|
|
50
|
+
# Small object to track exchange properties for logging purposes
|
51
|
+
Exchange = Struct.new(:name, :options)
|
52
|
+
|
53
|
+
# Queue class to provide extended logging in development mode
|
54
|
+
class Queue
|
55
|
+
def initialize(logger, name)
|
56
|
+
@logger = logger
|
57
|
+
@name = name
|
58
|
+
end
|
59
|
+
|
60
|
+
def bind(exchange, options)
|
61
|
+
@logger.debug "Bound queue #{@name}: #{exchange}, #{options.inspect}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def subscribe(options)
|
65
|
+
@logger.debug "Subscribed to queue #{@name}: #{options.inspect}"
|
66
|
+
@logger.warn 'This is a Warren::Handler::Log no messages will be processed'
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
28
71
|
attr_reader :logger
|
29
72
|
|
30
73
|
def initialize(logger:, routing_key_prefix: nil)
|
74
|
+
super()
|
31
75
|
@logger = logger
|
32
76
|
@routing_key_template = Handler.routing_key_template(routing_key_prefix)
|
33
77
|
end
|
34
78
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
def connect; end
|
40
|
-
|
41
|
-
def disconnect; end
|
79
|
+
def new_channel
|
80
|
+
Channel.new(@logger, routing_key_template: @routing_key_template)
|
81
|
+
end
|
42
82
|
|
43
83
|
#
|
44
84
|
# Yields a Warren::Log::Channel
|
@@ -48,7 +88,7 @@ module Warren
|
|
48
88
|
#
|
49
89
|
# @yieldreturn [Warren::Log::Channel] A rabbitMQ channel that logs messaged to the test warren
|
50
90
|
def with_channel
|
51
|
-
yield
|
91
|
+
yield new_channel
|
52
92
|
end
|
53
93
|
|
54
94
|
#
|
data/lib/warren/handler/test.rb
CHANGED
@@ -1,10 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base'
|
4
|
+
|
3
5
|
module Warren
|
4
6
|
module Handler
|
5
7
|
# Class Warren::Test provides provides a dummy RabbitMQ
|
6
|
-
# connection pool for use during testing
|
7
|
-
|
8
|
+
# connection pool for use during testing.
|
9
|
+
#
|
10
|
+
# = Set up a test warren
|
11
|
+
#
|
12
|
+
# By default, the test warren is disabled during testing to avoid storing
|
13
|
+
# messages unnecessarily. Instead you must explicitly enable it when you
|
14
|
+
# wish to test message receipt.
|
15
|
+
#
|
16
|
+
# If using rspec it is suggested that you add the following to your
|
17
|
+
# spec_helper.rb
|
18
|
+
#
|
19
|
+
# config.around(:each, warren: true) do |ex|
|
20
|
+
# Warren.handler.enable!
|
21
|
+
# ex.run
|
22
|
+
# Warren.handler.disable!
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# = Making assertions
|
26
|
+
#
|
27
|
+
# It is possible to query the test warren about the messages it has seen.
|
28
|
+
# In particular the following methods are useful:
|
29
|
+
#
|
30
|
+
# {render:#messages}
|
31
|
+
#
|
32
|
+
# {render:#last_message}
|
33
|
+
#
|
34
|
+
# {render:#message_count}
|
35
|
+
#
|
36
|
+
# {render:#messages_matching}
|
37
|
+
#
|
38
|
+
# = Example
|
39
|
+
#
|
40
|
+
# describe QcResult, warren: true do
|
41
|
+
# let(:warren) { Warren.handler }
|
42
|
+
#
|
43
|
+
# setup { warren.clear_messages }
|
44
|
+
# let(:resource) { build :qc_result }
|
45
|
+
# let(:routing_key) { 'test.message.qc_result.' }
|
46
|
+
#
|
47
|
+
# it 'broadcasts the resource' do
|
48
|
+
# resource.save!
|
49
|
+
# expect(warren.messages_matching(routing_key)).to eq(1)
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
class Test < Warren::Handler::Base
|
53
|
+
# Warning displayed if the user attempts to make assertions against the
|
54
|
+
# handler without having enabled it.
|
8
55
|
DISABLED_WARNING = <<~DISABLED_WARREN
|
9
56
|
Test made against a disabled warren.
|
10
57
|
Warren::Handler::Test must be explicitly enabled to track messages,
|
@@ -23,17 +70,25 @@ module Warren
|
|
23
70
|
|
24
71
|
You can then tag tests with warren: true to enable warren testing.
|
25
72
|
DISABLED_WARREN
|
26
|
-
# Stand in for {
|
73
|
+
# Stand in for {Broadcast::Channel}, provides a store of messages to use
|
27
74
|
# in test assertions
|
28
75
|
class Channel
|
29
76
|
def initialize(warren)
|
30
77
|
@warren = warren
|
31
78
|
end
|
32
79
|
|
80
|
+
# Records `message` for testing purposes
|
81
|
+
#
|
82
|
+
# @param message [#routing_key,#payload] A message should respond to routing_key and payload.
|
83
|
+
# @see Warren::Message::Full
|
84
|
+
#
|
85
|
+
# @return [Warren::Handler::Broadcast::Channel] returns self for chaining
|
86
|
+
#
|
33
87
|
def <<(message)
|
34
88
|
@warren << message
|
35
89
|
end
|
36
90
|
end
|
91
|
+
|
37
92
|
#
|
38
93
|
# Creates a test warren with no messages.
|
39
94
|
# Test warrens are shared across all threads.
|
@@ -41,45 +96,74 @@ module Warren
|
|
41
96
|
# @param [_] _args Configuration arguments are ignored.
|
42
97
|
#
|
43
98
|
def initialize(*_args)
|
99
|
+
super()
|
44
100
|
@messages = []
|
101
|
+
@exchanges = []
|
45
102
|
@enabled = false
|
46
103
|
end
|
47
104
|
|
48
105
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
def connect; end
|
53
|
-
|
54
|
-
def disconnect; end
|
55
|
-
|
56
|
-
#
|
57
|
-
# Yields an exchange which gets returned to the pool on block closure
|
58
|
-
#
|
106
|
+
# Yields a new channel, which proxies all message back to {messages} on the
|
107
|
+
# {Warren::Handler::Test}
|
59
108
|
#
|
60
109
|
# @return [void]
|
61
110
|
#
|
62
111
|
# @yieldreturn [Warren::Test::Channel] A rabbitMQ channel that logs messaged to the test warren
|
63
112
|
def with_channel
|
64
|
-
yield
|
113
|
+
yield new_channel
|
65
114
|
end
|
66
115
|
|
116
|
+
#
|
117
|
+
# Returns a new channel, which proxies all message back to {messages} on the
|
118
|
+
# {Warren::Handler::Test}
|
119
|
+
#
|
120
|
+
# @return [Warren::Test::Channel] A rabbitMQ channel that logs messaged to the test warren
|
121
|
+
#
|
122
|
+
def new_channel
|
123
|
+
Channel.new(@logger, routing_key_template: @routing_key_template)
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Clear any logged messaged
|
128
|
+
#
|
129
|
+
# @return [Array] The new empty array, lacking messages
|
130
|
+
#
|
67
131
|
def clear_messages
|
68
132
|
@messages = []
|
133
|
+
@exchanges = []
|
69
134
|
end
|
70
135
|
|
136
|
+
#
|
137
|
+
# Returns the last message received by the warren
|
138
|
+
#
|
139
|
+
# @return [#routing_key#payload] The last message object received by the warren
|
140
|
+
#
|
71
141
|
def last_message
|
72
142
|
messages.last
|
73
143
|
end
|
74
144
|
|
145
|
+
#
|
146
|
+
# Returns the total number message received by the warren since it was enabled
|
147
|
+
#
|
148
|
+
# @return [Integer] The total number of messages
|
149
|
+
#
|
75
150
|
def message_count
|
76
151
|
messages.length
|
77
152
|
end
|
78
153
|
|
154
|
+
#
|
155
|
+
# Returns the total number message received by the warren matching the given
|
156
|
+
# routing_key since it was enabled
|
157
|
+
#
|
158
|
+
# @param routing_key [String] The routing key to filter by
|
159
|
+
#
|
160
|
+
# @return [Integer] The number of matching messages
|
161
|
+
#
|
79
162
|
def messages_matching(routing_key)
|
80
163
|
messages.count { |message| message.routing_key == routing_key }
|
81
164
|
end
|
82
165
|
|
166
|
+
# Enable the warren
|
83
167
|
def enable!
|
84
168
|
@enabled = true
|
85
169
|
clear_messages
|
@@ -91,6 +175,9 @@ module Warren
|
|
91
175
|
clear_messages
|
92
176
|
end
|
93
177
|
|
178
|
+
# Returns an array of all message received by the warren since it was enabled
|
179
|
+
#
|
180
|
+
# @return [Array<#routing_key#payload>] All received messages
|
94
181
|
def messages
|
95
182
|
raise_if_not_tracking
|
96
183
|
@messages
|