pebbles-river 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pebbles/river.rb +0 -1
- data/lib/pebbles/river/river.rb +29 -21
- data/lib/pebbles/river/routing.rb +35 -2
- data/lib/pebbles/river/version.rb +1 -1
- data/lib/pebbles/river/worker.rb +12 -3
- data/spec/lib/routing_spec.rb +58 -6
- metadata +3 -6
- data/lib/pebbles/river/subscription.rb +0 -44
- data/spec/lib/subscription_spec.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2e7d4334cd98eebaa0bb87f18ff27cb94c373bf
|
4
|
+
data.tar.gz: 21845c17a52a8c30aa620cf77e7d872fda365751
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9757df6f8aa96fafca5646fd75dc0b31741d80b6c49a5713b70c6a16c2a26a6fa5ec9c5a1e12bf2b6d619b99a891923f0671364f6372f83b43568b56e27bbc7
|
7
|
+
data.tar.gz: 734552e446937b0016bf4aded20221f477fc6083f6e4f378c0c633bd74f40cf7cbe38e23e203146176af684f7a374ffa62c06836d245868d449f2aeabbef89b3
|
data/lib/pebbles/river.rb
CHANGED
@@ -9,7 +9,6 @@ require_relative "river/version"
|
|
9
9
|
require_relative "river/errors"
|
10
10
|
require_relative "river/message"
|
11
11
|
require_relative "river/worker"
|
12
|
-
require_relative "river/subscription"
|
13
12
|
require_relative "river/supervisor"
|
14
13
|
require_relative "river/routing"
|
15
14
|
require_relative "river/river"
|
data/lib/pebbles/river/river.rb
CHANGED
@@ -8,12 +8,18 @@ module Pebbles
|
|
8
8
|
attr_reader :session
|
9
9
|
attr_reader :channel
|
10
10
|
attr_reader :prefetch
|
11
|
+
attr_reader :exchange_name
|
11
12
|
|
12
13
|
def initialize(options = {})
|
13
14
|
options = {environment: options} if options.is_a?(String) # Backwards compatibility
|
14
15
|
|
15
16
|
@environment = (options[:environment] || ENV['RACK_ENV'] || 'development').dup.freeze
|
17
|
+
|
18
|
+
@exchange_name = 'pebblebed.river'
|
19
|
+
@exchange_name << ".#{environment}" if @environment != 'production'
|
20
|
+
|
16
21
|
@last_connect_attempt = nil
|
22
|
+
|
17
23
|
@prefetch = options[:prefetch]
|
18
24
|
end
|
19
25
|
|
@@ -25,15 +31,13 @@ module Pebbles
|
|
25
31
|
unless @session and @channel and @exchange
|
26
32
|
disconnect
|
27
33
|
handle_session_error do
|
28
|
-
session = Bunny::Session.new(::Pebbles::River.rabbitmq_options)
|
29
|
-
session.start
|
30
|
-
|
31
|
-
channel = session.create_channel
|
32
|
-
channel.prefetch(@prefetch) if @prefetch
|
34
|
+
@session = Bunny::Session.new(::Pebbles::River.rabbitmq_options)
|
35
|
+
@session.start
|
33
36
|
|
34
|
-
|
37
|
+
@channel = @session.create_channel
|
38
|
+
@channel.prefetch(@prefetch) if @prefetch
|
35
39
|
|
36
|
-
@
|
40
|
+
@exchange = @channel.exchange(@exchange_name, EXCHANGE_OPTIONS.dup)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -70,16 +74,27 @@ module Pebbles
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def queue(options = {})
|
77
|
+
options.assert_valid_keys(:name, :ttl, :event, :path, :klass,
|
78
|
+
:dead_letter_routing_key, :routing_key)
|
79
|
+
|
73
80
|
raise ArgumentError.new 'Queue must be named' unless options[:name]
|
74
81
|
|
75
|
-
|
82
|
+
queue_args = {}
|
76
83
|
if (ttl = options[:ttl])
|
77
|
-
|
84
|
+
queue_args['x-message-ttl'] = ttl
|
85
|
+
end
|
86
|
+
if (dead_letter_routing_key = options[:dead_letter_routing_key])
|
87
|
+
queue_args['x-dead-letter-exchange'] = @exchange_name
|
88
|
+
queue_args['x-dead-letter-routing-key'] = dead_letter_routing_key
|
78
89
|
end
|
90
|
+
queue_opts = {durable: true, arguments: queue_args}
|
79
91
|
|
80
92
|
connect
|
81
93
|
queue = @channel.queue(options[:name], queue_opts)
|
82
|
-
|
94
|
+
if (routing_key = options[:routing_key])
|
95
|
+
queue.bind(exchange.name, key: routing_key)
|
96
|
+
end
|
97
|
+
Routing.binding_routing_keys_for(options.slice(:event, :class, :path)).each do |key|
|
83
98
|
queue.bind(exchange.name, key: key)
|
84
99
|
end
|
85
100
|
queue
|
@@ -87,20 +102,10 @@ module Pebbles
|
|
87
102
|
|
88
103
|
private
|
89
104
|
|
90
|
-
def exchange_name
|
91
|
-
return @exchange_name ||= format_exchange_name
|
92
|
-
end
|
93
|
-
|
94
|
-
def format_exchange_name
|
95
|
-
name = 'pebblebed.river'
|
96
|
-
name << ".#{environment}" if @environment != 'production'
|
97
|
-
name
|
98
|
-
end
|
99
|
-
|
100
105
|
def handle_session_error(exception_klass = ConnectFailure, &block)
|
101
106
|
last_exception = nil
|
102
107
|
Timeout.timeout(MAX_RETRY_TIMEOUT) do
|
103
|
-
|
108
|
+
retry_count = 0
|
104
109
|
begin
|
105
110
|
yield
|
106
111
|
rescue *CONNECTION_EXCEPTIONS => exception
|
@@ -112,6 +117,9 @@ module Pebbles
|
|
112
117
|
end
|
113
118
|
end
|
114
119
|
rescue Timeout::Error => timeout_exception
|
120
|
+
# Timeouts can screw up the connection, so forcibly close it
|
121
|
+
disconnect
|
122
|
+
|
115
123
|
if last_exception
|
116
124
|
raise exception_klass.new(last_exception.message, last_exception)
|
117
125
|
else
|
@@ -4,7 +4,6 @@ module Pebbles
|
|
4
4
|
|
5
5
|
def self.routing_key_for(options)
|
6
6
|
options.assert_valid_keys(:uid, :event)
|
7
|
-
|
8
7
|
raise ArgumentError.new(':event is required') unless options[:event]
|
9
8
|
raise ArgumentError.new(':uid is required') unless options[:uid]
|
10
9
|
|
@@ -13,6 +12,40 @@ module Pebbles
|
|
13
12
|
key.join('._.')
|
14
13
|
end
|
15
14
|
|
15
|
+
def self.binding_routing_keys_for(options)
|
16
|
+
options.assert_valid_keys(:path, :class, :event)
|
17
|
+
keys = []
|
18
|
+
if options[:event] or options[:path] or options[:class]
|
19
|
+
element_to_routing_key_parts(options[:event]).each do |event|
|
20
|
+
element_to_routing_key_parts(options[:class]).each do |klass|
|
21
|
+
element_to_routing_key_parts(options[:path]).each do |pathspec|
|
22
|
+
path_to_routing_key_parts(pathspec).each do |path|
|
23
|
+
keys << [event, klass, path].join('._.')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
keys
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.path_to_routing_key_parts(s)
|
35
|
+
required, optional = s.split('^').map { |p| p.split('.') }
|
36
|
+
required = Array(required.join('.'))
|
37
|
+
optional ||= []
|
38
|
+
(0..optional.length).map {|i| required + optional[0,i]}.map {|p| p.join('.')}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.element_to_routing_key_parts(s)
|
42
|
+
s ||= '#'
|
43
|
+
if s.respond_to?(:to_a)
|
44
|
+
s = s.join('|')
|
45
|
+
end
|
46
|
+
s.gsub('**', '#').split('|')
|
47
|
+
end
|
48
|
+
|
16
49
|
end
|
17
50
|
end
|
18
|
-
end
|
51
|
+
end
|
data/lib/pebbles/river/worker.rb
CHANGED
@@ -50,6 +50,7 @@ module Pebbles
|
|
50
50
|
else
|
51
51
|
@managed_acking = true
|
52
52
|
end
|
53
|
+
@dead_lettered = !!@queue_options[:dead_letter_routing_key]
|
53
54
|
@on_exception = options[:on_exception] || ->(*args) { }
|
54
55
|
@handler = handler
|
55
56
|
@river = River.new(options.slice(:prefetch))
|
@@ -137,7 +138,7 @@ module Pebbles
|
|
137
138
|
message = Message.new(content, delivery_info, queue)
|
138
139
|
rescue => exception
|
139
140
|
ignore_exceptions do
|
140
|
-
|
141
|
+
reject(delivery_info)
|
141
142
|
end
|
142
143
|
raise exception
|
143
144
|
else
|
@@ -148,7 +149,7 @@ module Pebbles
|
|
148
149
|
rescue => exception
|
149
150
|
if @managed_acking
|
150
151
|
ignore_exceptions do
|
151
|
-
|
152
|
+
reject(delivery_info)
|
152
153
|
end
|
153
154
|
end
|
154
155
|
raise exception
|
@@ -156,7 +157,7 @@ module Pebbles
|
|
156
157
|
if @managed_acking
|
157
158
|
case result
|
158
159
|
when false
|
159
|
-
|
160
|
+
reject(delivery_info)
|
160
161
|
else
|
161
162
|
message.ack
|
162
163
|
end
|
@@ -165,6 +166,14 @@ module Pebbles
|
|
165
166
|
end
|
166
167
|
end
|
167
168
|
|
169
|
+
def reject(delivery_info)
|
170
|
+
# Normally requeue, except if we are dead-lettering to another queue, where
|
171
|
+
# requeue = false means to bounce it to DLX.
|
172
|
+
requeue = !@dead_lettered
|
173
|
+
|
174
|
+
queue.channel.reject(delivery_info.delivery_tag.to_i, requeue)
|
175
|
+
end
|
176
|
+
|
168
177
|
def with_exceptions(&block)
|
169
178
|
begin
|
170
179
|
yield
|
data/spec/lib/routing_spec.rb
CHANGED
@@ -6,20 +6,72 @@ describe Pebbles::River::Routing do
|
|
6
6
|
Pebbles::River::Routing
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
10
|
-
|
9
|
+
describe '#routing_key_for' do
|
11
10
|
specify do
|
12
|
-
options = {:
|
11
|
+
options = {event: 'created', :uid => 'post.awesome.event:feeds.bagera.whatevs$123'}
|
13
12
|
subject.routing_key_for(options).should eq('created._.post.awesome.event._.feeds.bagera.whatevs')
|
14
13
|
end
|
15
14
|
|
16
|
-
specify
|
15
|
+
specify 'event is required' do
|
17
16
|
->{ subject.routing_key_for(:uid => 'whatevs') }.should raise_error ArgumentError
|
18
17
|
end
|
19
18
|
|
20
|
-
specify
|
21
|
-
->{ subject.routing_key_for(:
|
19
|
+
specify 'uid is required' do
|
20
|
+
->{ subject.routing_key_for(event: 'whatevs') }.should raise_error ArgumentError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#binding_routing_keys_for' do
|
25
|
+
specify 'simple, direct match' do
|
26
|
+
expect(subject.binding_routing_keys_for(event: 'create', class: 'post.event', path: 'feed.bagera')).to eq [
|
27
|
+
'create._.post.event._.feed.bagera'
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
specify 'simple wildcard match' do
|
32
|
+
options = {event: '*.create', class: 'post.*', path: '*.bagera.*'}
|
33
|
+
expect(subject.binding_routing_keys_for(options)).to eq(['*.create._.post.*._.*.bagera.*'])
|
22
34
|
end
|
23
35
|
|
36
|
+
describe "anything matchers" do
|
37
|
+
specify 'match anything (duh)' do
|
38
|
+
expect(subject.binding_routing_keys_for({event: '**', class: '**', path: '**'})).to eq(['#._.#._.#'])
|
39
|
+
end
|
40
|
+
|
41
|
+
specify 'match nothing if not specified' do
|
42
|
+
expect(subject.binding_routing_keys_for({})).to eq []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'handles "or" queries' do
|
47
|
+
options = {event: 'create|delete', class: 'post', path: 'bagera|bandwagon'}
|
48
|
+
expected = ['create._.post._.bagera', 'delete._.post._.bagera', 'create._.post._.bandwagon', 'delete._.post._.bandwagon'].sort
|
49
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
50
|
+
end
|
51
|
+
|
52
|
+
# FIXME
|
53
|
+
# describe "optional paths" do
|
54
|
+
# it { Subscription.new.pathify('a.b').should eq(['a.b']) }
|
55
|
+
# it { Subscription.new.pathify('a.^b.c').should eq(%w(a a.b a.b.c)) }
|
56
|
+
# end
|
57
|
+
|
58
|
+
it "handles optional queries" do
|
59
|
+
options = {event: 'create', class: 'post', path: 'feeds.bagera.^fb.concerts'}
|
60
|
+
expected = ['create._.post._.feeds.bagera', 'create._.post._.feeds.bagera.fb', 'create._.post._.feeds.bagera.fb.concerts'].sort
|
61
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "combines all kinds of weird stuff" do
|
65
|
+
options = {event: 'create', class: 'post', path: 'a.^b.c|x.^y.z'}
|
66
|
+
expected = [
|
67
|
+
'create._.post._.a',
|
68
|
+
'create._.post._.a.b',
|
69
|
+
'create._.post._.a.b.c',
|
70
|
+
'create._.post._.x',
|
71
|
+
'create._.post._.x.y',
|
72
|
+
'create._.post._.x.y.z',
|
73
|
+
].sort
|
74
|
+
expect(subject.binding_routing_keys_for(options).sort).to eq(expected)
|
75
|
+
end
|
24
76
|
end
|
25
77
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pebbles-river
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Staubo
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-07-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pebblebed
|
@@ -157,14 +157,12 @@ files:
|
|
157
157
|
- lib/pebbles/river/rate_limiter.rb
|
158
158
|
- lib/pebbles/river/river.rb
|
159
159
|
- lib/pebbles/river/routing.rb
|
160
|
-
- lib/pebbles/river/subscription.rb
|
161
160
|
- lib/pebbles/river/supervisor.rb
|
162
161
|
- lib/pebbles/river/version.rb
|
163
162
|
- lib/pebbles/river/worker.rb
|
164
163
|
- pebbles-river.gemspec
|
165
164
|
- spec/lib/river_spec.rb
|
166
165
|
- spec/lib/routing_spec.rb
|
167
|
-
- spec/lib/subscription_spec.rb
|
168
166
|
- spec/lib/worker_spec.rb
|
169
167
|
- spec/spec_helper.rb
|
170
168
|
homepage: ''
|
@@ -187,14 +185,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
185
|
version: '0'
|
188
186
|
requirements: []
|
189
187
|
rubyforge_project:
|
190
|
-
rubygems_version: 2.
|
188
|
+
rubygems_version: 2.2.5
|
191
189
|
signing_key:
|
192
190
|
specification_version: 4
|
193
191
|
summary: Implements an event river mechanism for Pebblebed.
|
194
192
|
test_files:
|
195
193
|
- spec/lib/river_spec.rb
|
196
194
|
- spec/lib/routing_spec.rb
|
197
|
-
- spec/lib/subscription_spec.rb
|
198
195
|
- spec/lib/worker_spec.rb
|
199
196
|
- spec/spec_helper.rb
|
200
197
|
has_rdoc:
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module Pebbles
|
2
|
-
module River
|
3
|
-
|
4
|
-
class Subscription
|
5
|
-
|
6
|
-
attr_reader :events, :klasses, :paths
|
7
|
-
|
8
|
-
def initialize(options = {})
|
9
|
-
@events = querify(options[:event]).split('|')
|
10
|
-
@paths = querify(options[:path]).split('|')
|
11
|
-
@klasses = querify(options[:klass]).split('|')
|
12
|
-
end
|
13
|
-
|
14
|
-
def queries
|
15
|
-
qx = []
|
16
|
-
# If we add more than one more level,
|
17
|
-
# it's probably time to go recursive.
|
18
|
-
events.each do |event|
|
19
|
-
klasses.each do |klass|
|
20
|
-
paths.each do |pathspec|
|
21
|
-
pathify(pathspec).each do |path|
|
22
|
-
qx << [event, klass, path].join('._.')
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
qx
|
28
|
-
end
|
29
|
-
|
30
|
-
def querify(query)
|
31
|
-
(query || '#').gsub('**', '#')
|
32
|
-
end
|
33
|
-
|
34
|
-
def pathify(s)
|
35
|
-
required, optional = s.split('^').map {|s| s.split('.')}
|
36
|
-
required = Array(required.join('.'))
|
37
|
-
optional ||= []
|
38
|
-
(0..optional.length).map {|i| required + optional[0,i]}.map {|p| p.join('.')}
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
include Pebbles::River
|
4
|
-
|
5
|
-
describe Subscription do
|
6
|
-
|
7
|
-
specify 'simple, direct match' do
|
8
|
-
options = {:event => 'create', :klass => 'post.event', :path => 'feed.bagera'}
|
9
|
-
subscription = Subscription.new(options)
|
10
|
-
subscription.queries.should eq(['create._.post.event._.feed.bagera'])
|
11
|
-
end
|
12
|
-
|
13
|
-
specify 'simple wildcard match' do
|
14
|
-
options = {:event => '*.create', :klass => 'post.*', :path => '*.bagera.*'}
|
15
|
-
Subscription.new(options).queries.should eq(['*.create._.post.*._.*.bagera.*'])
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "anything matchers" do
|
19
|
-
|
20
|
-
specify 'match anything (duh)' do
|
21
|
-
options = {:event => '**', :klass => '**', :path => '**'}
|
22
|
-
Subscription.new(options).queries.should eq(['#._.#._.#'])
|
23
|
-
end
|
24
|
-
|
25
|
-
specify 'match anything if not specified' do
|
26
|
-
Subscription.new.queries.should eq(['#._.#._.#'])
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'handles "or" queries' do
|
32
|
-
options = {:event => 'create|delete', :klass => 'post', :path => 'bagera|bandwagon'}
|
33
|
-
expected = ['create._.post._.bagera', 'delete._.post._.bagera', 'create._.post._.bandwagon', 'delete._.post._.bandwagon'].sort
|
34
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
35
|
-
end
|
36
|
-
|
37
|
-
describe "optional paths" do
|
38
|
-
it { Subscription.new.pathify('a.b').should eq(['a.b']) }
|
39
|
-
it { Subscription.new.pathify('a.^b.c').should eq(%w(a a.b a.b.c)) }
|
40
|
-
end
|
41
|
-
|
42
|
-
it "handles optional queries" do
|
43
|
-
options = {:event => 'create', :klass => 'post', :path => 'feeds.bagera.^fb.concerts'}
|
44
|
-
expected = ['create._.post._.feeds.bagera', 'create._.post._.feeds.bagera.fb', 'create._.post._.feeds.bagera.fb.concerts'].sort
|
45
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "combines all kinds of weird stuff" do
|
49
|
-
options = {:event => 'create', :klass => 'post', :path => 'a.^b.c|x.^y.z'}
|
50
|
-
expected = [
|
51
|
-
'create._.post._.a',
|
52
|
-
'create._.post._.a.b',
|
53
|
-
'create._.post._.a.b.c',
|
54
|
-
'create._.post._.x',
|
55
|
-
'create._.post._.x.y',
|
56
|
-
'create._.post._.x.y.z',
|
57
|
-
].sort
|
58
|
-
Subscription.new(options).queries.sort.should eq(expected)
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|