distribot 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +10 -0
- data/Dockerfile +9 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +153 -0
- data/LICENSE +201 -0
- data/README.md +107 -0
- data/Rakefile +16 -0
- data/bin/distribot.flow-created +6 -0
- data/bin/distribot.flow-finished +6 -0
- data/bin/distribot.handler-finished +6 -0
- data/bin/distribot.phase-finished +6 -0
- data/bin/distribot.phase-started +6 -0
- data/bin/distribot.task-finished +6 -0
- data/distribot.gemspec +35 -0
- data/docker-compose.yml +29 -0
- data/examples/controller +168 -0
- data/examples/distribot.eye +49 -0
- data/examples/status +38 -0
- data/examples/worker +135 -0
- data/lib/distribot/connector.rb +162 -0
- data/lib/distribot/flow.rb +200 -0
- data/lib/distribot/flow_created_handler.rb +12 -0
- data/lib/distribot/flow_finished_handler.rb +12 -0
- data/lib/distribot/handler.rb +40 -0
- data/lib/distribot/handler_finished_handler.rb +29 -0
- data/lib/distribot/phase.rb +46 -0
- data/lib/distribot/phase_finished_handler.rb +19 -0
- data/lib/distribot/phase_handler.rb +15 -0
- data/lib/distribot/phase_started_handler.rb +69 -0
- data/lib/distribot/task_finished_handler.rb +37 -0
- data/lib/distribot/worker.rb +148 -0
- data/lib/distribot.rb +108 -0
- data/provision/nodes.sh +80 -0
- data/provision/templates/fluentd.conf +27 -0
- data/spec/distribot/bunny_connector_spec.rb +196 -0
- data/spec/distribot/connection_sharer_spec.rb +34 -0
- data/spec/distribot/connector_spec.rb +63 -0
- data/spec/distribot/flow_created_handler_spec.rb +32 -0
- data/spec/distribot/flow_finished_handler_spec.rb +32 -0
- data/spec/distribot/flow_spec.rb +661 -0
- data/spec/distribot/handler_finished_handler_spec.rb +112 -0
- data/spec/distribot/handler_spec.rb +32 -0
- data/spec/distribot/module_spec.rb +163 -0
- data/spec/distribot/multi_subscription_spec.rb +37 -0
- data/spec/distribot/phase_finished_handler_spec.rb +61 -0
- data/spec/distribot/phase_started_handler_spec.rb +150 -0
- data/spec/distribot/subscription_spec.rb +40 -0
- data/spec/distribot/task_finished_handler_spec.rb +71 -0
- data/spec/distribot/worker_spec.rb +281 -0
- data/spec/fixtures/simple_flow.json +49 -0
- data/spec/spec_helper.rb +74 -0
- metadata +371 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::HandlerFinishedHandler do
|
4
|
+
before do
|
5
|
+
Distribot.stub(:subscribe)
|
6
|
+
end
|
7
|
+
describe 'definition' do
|
8
|
+
it 'subscribes to the correct queue' do
|
9
|
+
expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.handler.finished'
|
10
|
+
end
|
11
|
+
it 'declares a valid handler' do
|
12
|
+
expect(Distribot::Handler.handler_for(described_class)).to eq :callback
|
13
|
+
end
|
14
|
+
it 'has a method matching the handler name' do
|
15
|
+
expect(described_class.new).to respond_to :callback
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#callback(message)' do
|
20
|
+
before do
|
21
|
+
@message = {
|
22
|
+
task_queue: 'task-queue',
|
23
|
+
flow_id: 'flow-id',
|
24
|
+
phase: 'phase1'
|
25
|
+
}
|
26
|
+
@handler = described_class.new
|
27
|
+
end
|
28
|
+
context 'when all the handler\'s tasks' do
|
29
|
+
context 'are complete' do
|
30
|
+
before do
|
31
|
+
expect(Distribot::Flow).to receive(:find).with(@message[:flow_id]) do
|
32
|
+
Distribot::Flow.new(id: @message[:flow_id], phases: [{name:'phase1', handlers: []}])
|
33
|
+
end
|
34
|
+
expect(@handler).to receive(:all_phase_handler_tasks_are_complete?){ true }
|
35
|
+
end
|
36
|
+
it 'announces that this phase has completed and that everyone should stop consuming its task.finished queue' do
|
37
|
+
# Describe what 'nothing' looks like:
|
38
|
+
expect(Distribot).to receive(:publish!).with('distribot.flow.phase.finished', {
|
39
|
+
flow_id: @message[:flow_id],
|
40
|
+
phase: 'phase1'
|
41
|
+
})
|
42
|
+
|
43
|
+
# Finally:
|
44
|
+
@handler.callback(@message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context 'are not yet complete' do
|
48
|
+
before do
|
49
|
+
expect(Distribot::Flow).to receive(:find).with(@message[:flow_id]) do
|
50
|
+
Distribot::Flow.new(id: @message[:flow_id], phases: [{name:'phase1', handlers: []}])
|
51
|
+
end
|
52
|
+
expect(@handler).to receive(:all_phase_handler_tasks_are_complete?){ false }
|
53
|
+
end
|
54
|
+
it 'does nothing' do
|
55
|
+
# Describe what 'nothing' looks like:
|
56
|
+
expect(Distribot).not_to receive(:publish!)
|
57
|
+
expect(Distribot).not_to receive(:broadcast!)
|
58
|
+
|
59
|
+
# Finally:
|
60
|
+
@handler.callback(@message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#all_phase_handler_tasks_are_complete?(flow, phase)' do
|
67
|
+
context 'when all tasks are complete' do
|
68
|
+
before do
|
69
|
+
@flow = Distribot::Flow.new(id: 123)
|
70
|
+
handlers = ['handler1']
|
71
|
+
task_counts = {
|
72
|
+
"distribot.flow.123.phase1.handler1.tasks" => 0,
|
73
|
+
}
|
74
|
+
@phase = Distribot::Phase.new(name: 'phase1', handlers: [
|
75
|
+
'handler1'
|
76
|
+
])
|
77
|
+
expect(Distribot).to receive(:redis) do
|
78
|
+
redis = double('redis')
|
79
|
+
expect(redis).to receive(:get).exactly(1).times do |key|
|
80
|
+
task_counts[key]
|
81
|
+
end
|
82
|
+
redis
|
83
|
+
end
|
84
|
+
end
|
85
|
+
it 'returns true' do
|
86
|
+
expect(described_class.new.all_phase_handler_tasks_are_complete?(@flow, @phase)).to be_truthy
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context 'when some tasks remain' do
|
90
|
+
before do
|
91
|
+
@flow = Distribot::Flow.new(id: 123)
|
92
|
+
handlers = ['handler1']
|
93
|
+
task_counts = {
|
94
|
+
"distribot.flow.123.phase1.handler1.tasks" => 1,
|
95
|
+
}
|
96
|
+
@phase = Distribot::Phase.new(name: 'phase1', handlers: [
|
97
|
+
'handler1'
|
98
|
+
])
|
99
|
+
expect(Distribot).to receive(:redis) do
|
100
|
+
redis = double('redis')
|
101
|
+
expect(redis).to receive(:get).exactly(1).times do |key|
|
102
|
+
task_counts[key]
|
103
|
+
end
|
104
|
+
redis
|
105
|
+
end
|
106
|
+
end
|
107
|
+
it 'returns false' do
|
108
|
+
expect(described_class.new.all_phase_handler_tasks_are_complete?(@flow, @phase)).to be_falsey
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::Handler do
|
4
|
+
before do
|
5
|
+
Distribot.stub(:subscribe)
|
6
|
+
Distribot.stub(:subscribe_multi)
|
7
|
+
end
|
8
|
+
describe '.subscribe_to' do
|
9
|
+
before do
|
10
|
+
@id = SecureRandom.hex(8)
|
11
|
+
@queue_name = "queue-#{@id}"
|
12
|
+
@klass_name = "Foo#{@id}"
|
13
|
+
expect(Distribot).to receive(:subscribe).with(@queue_name, {}) do |queue_name, args, &block|
|
14
|
+
block.call(queue_name, args, block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
it 'subscribes to the queue provided' do
|
18
|
+
eval <<-EOF
|
19
|
+
class #{@klass_name}
|
20
|
+
include Distribot::Handler
|
21
|
+
subscribe_to '#{@queue_name}', handler: :callback
|
22
|
+
def callback(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
EOF
|
26
|
+
@klass_ref = Kernel.const_get(@klass_name)
|
27
|
+
@klass_ref.new
|
28
|
+
expect(@klass_ref.handler).to eq :callback
|
29
|
+
expect(@klass_ref.queue).to eq @queue_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot do
|
4
|
+
describe '.debug' do
|
5
|
+
it 'allows true and false values' do
|
6
|
+
Distribot.debug = true
|
7
|
+
expect(Distribot.debug).to be_truthy
|
8
|
+
Distribot.debug = false
|
9
|
+
expect(Distribot.debug).to be_falsey
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.configure' do
|
14
|
+
it 'executes the given block and uses the result as the configuration' do
|
15
|
+
Distribot.reset_configuration!
|
16
|
+
Distribot.configuration
|
17
|
+
Distribot.configure do |config|
|
18
|
+
config.foo=:bar
|
19
|
+
end
|
20
|
+
expect(Distribot.configuration.foo).to eq :bar
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.redis' do
|
25
|
+
context 'when configuration.redis_url' do
|
26
|
+
context 'is nil' do
|
27
|
+
before do
|
28
|
+
Distribot.reset_configuration!
|
29
|
+
Distribot.configure do |config|
|
30
|
+
config.redis_url = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it 'returns a new Redis instance without argument' do
|
34
|
+
expect(Redis).to receive(:new).with(no_args){ 'HELLO' }
|
35
|
+
expect(Distribot.redis).to eq 'HELLO'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context 'is given' do
|
39
|
+
before do
|
40
|
+
@redis_url = 'redis://foo:6379/0'
|
41
|
+
Distribot.reset_configuration!
|
42
|
+
Distribot.configure do |config|
|
43
|
+
config.redis_url = @redis_url
|
44
|
+
end
|
45
|
+
end
|
46
|
+
it 'returns a new Redis instance' do
|
47
|
+
expect(Redis).to receive(:new).with(url: @redis_url){ 'HELLO' }
|
48
|
+
expect(Distribot.redis).to eq 'HELLO'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '.connector' do
|
55
|
+
before do
|
56
|
+
Distribot.configure do |config|
|
57
|
+
config.rabbitmq_url = 'fake-rabbit-url'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
it 'returns a BunnyConnector' do
|
61
|
+
expect(Distribot::BunnyConnector).to receive(:new).with('fake-rabbit-url'){ 'connector' }
|
62
|
+
expect(Distribot.connector).to eq 'connector'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.queue_exists?(name)' do
|
67
|
+
context 'when the queue exists' do
|
68
|
+
before do
|
69
|
+
@queue_name = "queue-#{SecureRandom.uuid}"
|
70
|
+
expect(Distribot).to receive(:connector) do
|
71
|
+
connector = double('connector')
|
72
|
+
expect(connector).to receive(:queue_exists?).with(@queue_name){ true }
|
73
|
+
connector
|
74
|
+
end
|
75
|
+
end
|
76
|
+
it 'returns true' do
|
77
|
+
expect(Distribot.queue_exists?(@queue_name)).to be_truthy
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context 'when the queue does not exist' do
|
81
|
+
before do
|
82
|
+
@queue_name = "queue-#{SecureRandom.uuid}"
|
83
|
+
expect(Distribot).to receive(:connector) do
|
84
|
+
connector = double('connector')
|
85
|
+
expect(connector).to receive(:queue_exists?).with(@queue_name){ false }
|
86
|
+
connector
|
87
|
+
end
|
88
|
+
end
|
89
|
+
it 'returns false' do
|
90
|
+
expect(Distribot.queue_exists?(@queue_name)).to be_falsey
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '.subscribe(queue_name, options={}, &block)' do
|
96
|
+
before do
|
97
|
+
@topic = "queue-#{SecureRandom.uuid}"
|
98
|
+
expect(Distribot).to receive(:connector) do
|
99
|
+
connector = double('connector')
|
100
|
+
expect(connector).to receive(:subscribe).with(@topic, {}) do |&block|
|
101
|
+
block.call( hello: 'world' )
|
102
|
+
end
|
103
|
+
connector
|
104
|
+
end
|
105
|
+
end
|
106
|
+
it 'subscribes properly' do
|
107
|
+
Distribot.subscribe(@topic) do |message|
|
108
|
+
expect(message).to have_key :hello
|
109
|
+
expect(message[:hello]).to eq 'world'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '.subscribe_multi(topic, options={}, &block)' do
|
115
|
+
before do
|
116
|
+
@topic = "queue-#{SecureRandom.uuid}"
|
117
|
+
expect(Distribot).to receive(:connector) do
|
118
|
+
connector = double('connector')
|
119
|
+
expect(connector).to receive(:subscribe_multi).with(@topic, {}) do |&block|
|
120
|
+
block.call( hello: 'world' )
|
121
|
+
end
|
122
|
+
connector
|
123
|
+
end
|
124
|
+
end
|
125
|
+
it 'subscribes properly' do
|
126
|
+
Distribot.subscribe_multi(@topic) do |message|
|
127
|
+
expect(message).to have_key :hello
|
128
|
+
expect(message[:hello]).to eq 'world'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '.publish!(topic, data)' do
|
134
|
+
before do
|
135
|
+
@topic = SecureRandom.uuid
|
136
|
+
@data = { id: SecureRandom.uuid }
|
137
|
+
expect(Distribot).to receive(:connector) do
|
138
|
+
connector = double('connector')
|
139
|
+
expect(connector).to receive(:publish).with(@topic, @data)
|
140
|
+
connector
|
141
|
+
end
|
142
|
+
end
|
143
|
+
it 'publishes the message to the topic' do
|
144
|
+
Distribot.publish!(@topic, @data)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '.broadcast!(topic, data)' do
|
149
|
+
before do
|
150
|
+
@topic = SecureRandom.uuid
|
151
|
+
@data = { id: SecureRandom.uuid }
|
152
|
+
expect(Distribot).to receive(:connector) do
|
153
|
+
connector = double('connector')
|
154
|
+
expect(connector).to receive(:broadcast).with(@topic, @data)
|
155
|
+
connector
|
156
|
+
end
|
157
|
+
end
|
158
|
+
it 'publishes the message to the topic' do
|
159
|
+
Distribot.broadcast!(@topic, @data)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::MultiSubscription do
|
4
|
+
describe 'inheritance' do
|
5
|
+
let(:subject){ described_class.new(nil) }
|
6
|
+
it { should be_a Distribot::ConnectionSharer }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#start(topic, options={}, &block)' do
|
10
|
+
it 'subscribes to the rabbit queue' do
|
11
|
+
# Arrange:
|
12
|
+
@topic = 'my.topic'
|
13
|
+
subscription = described_class.new(nil)
|
14
|
+
|
15
|
+
queue = double('queue')
|
16
|
+
channel = double('channel')
|
17
|
+
expect(channel).to receive(:queue).with('', exclusive: true, auto_delete: true){ queue }
|
18
|
+
expect(channel).to receive(:fanout).with(@topic){ 'exchange' }
|
19
|
+
expect(queue).to receive(:bind).with('exchange') do
|
20
|
+
exchange = double('exchange')
|
21
|
+
expect(exchange).to receive(:subscribe) do |&block|
|
22
|
+
block.call(nil, nil, {id: :good_message} )
|
23
|
+
block.call(nil, nil, {id: :bad_message} )
|
24
|
+
end
|
25
|
+
exchange
|
26
|
+
end
|
27
|
+
expect(subscription).to receive(:channel).exactly(2).times{ channel }
|
28
|
+
|
29
|
+
# Act:
|
30
|
+
subscription.start(@topic) do |msg|
|
31
|
+
raise "Test error" if msg[:id] == 'bad_message'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Assert:
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::PhaseFinishedHandler do
|
4
|
+
describe 'definition' do
|
5
|
+
it 'subscribes to the correct queue' do
|
6
|
+
expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.phase.finished'
|
7
|
+
end
|
8
|
+
it 'declares a valid handler' do
|
9
|
+
expect(Distribot::Handler.handler_for(described_class)).to eq :callback
|
10
|
+
end
|
11
|
+
it 'has a method matching the handler name' do
|
12
|
+
expect(Distribot).to receive(:subscribe)
|
13
|
+
expect(described_class.new).to respond_to :callback
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#callback( :flow_id, :phase )' do
|
18
|
+
before do
|
19
|
+
expect(Distribot).to receive(:subscribe)
|
20
|
+
end
|
21
|
+
context 'when flow is' do
|
22
|
+
before do
|
23
|
+
@flow = double('flow')
|
24
|
+
@flow_id = 'xxx'
|
25
|
+
expect(Distribot::Flow).to receive(:find).with(@flow_id) { @flow }
|
26
|
+
end
|
27
|
+
context 'still in :phase' do
|
28
|
+
before do
|
29
|
+
expect(@flow).to receive(:current_phase){ 'start' }
|
30
|
+
end
|
31
|
+
context 'and the flow' do
|
32
|
+
context 'has a next phase' do
|
33
|
+
before do
|
34
|
+
expect(@flow).to receive(:next_phase).exactly(2).times{ 'finish' }
|
35
|
+
end
|
36
|
+
it 'tells the flow to transition to the next phase' do
|
37
|
+
expect(@flow).to receive(:transition_to!).with('finish')
|
38
|
+
described_class.new.callback(flow_id: @flow_id, phase: 'start')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context 'does not have a next phase' do
|
42
|
+
before do
|
43
|
+
expect(@flow).to receive(:next_phase){ nil }
|
44
|
+
expect(@flow).to receive(:id){ @flow_id }
|
45
|
+
end
|
46
|
+
it 'publishes to distribot.flow.finished' do
|
47
|
+
expect(Distribot).to receive(:publish!).with('distribot.flow.finished', {
|
48
|
+
flow_id: @flow_id
|
49
|
+
})
|
50
|
+
described_class.new.callback(flow_id: @flow_id, phase: 'start')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
context 'no longer in :phase' do
|
56
|
+
it 'does nothing'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::PhaseStartedHandler do
|
4
|
+
describe 'definition' do
|
5
|
+
it 'subscribes to the correct queue' do
|
6
|
+
expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.phase.started'
|
7
|
+
end
|
8
|
+
it 'declares a valid handler' do
|
9
|
+
expect(Distribot::Handler.handler_for(described_class)).to eq :callback
|
10
|
+
end
|
11
|
+
it 'has a method matching the handler name' do
|
12
|
+
expect(Distribot).to receive(:subscribe)
|
13
|
+
expect(described_class.new).to respond_to :callback
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#callback' do
|
18
|
+
before do
|
19
|
+
@flow = Distribot::Flow.new(
|
20
|
+
id: 1,
|
21
|
+
name: 'test',
|
22
|
+
phases: [{
|
23
|
+
name: 'phase1',
|
24
|
+
is_initial: true,
|
25
|
+
}]
|
26
|
+
)
|
27
|
+
expect(Distribot::Flow).to receive(:find).with(1){ @flow }
|
28
|
+
@phase = double('phase')
|
29
|
+
expect(@phase).to receive(:handlers).at_least(1).times{ @handlers }
|
30
|
+
expect(@flow).to receive(:phase).with('phase1'){ @phase }
|
31
|
+
end
|
32
|
+
context 'when this phase has' do
|
33
|
+
context 'no handlers' do
|
34
|
+
before do
|
35
|
+
@handlers = [ ]
|
36
|
+
expect(@phase).to receive(:name){ 'phase1' }
|
37
|
+
end
|
38
|
+
it 'considers this phase finished and publishes a message to that effect' do
|
39
|
+
expect(Distribot).to receive(:publish!).with('distribot.flow.phase.finished', {
|
40
|
+
flow_id: @flow.id,
|
41
|
+
phase: 'phase1'
|
42
|
+
})
|
43
|
+
expect(Distribot).to receive(:subscribe)
|
44
|
+
described_class.new.callback(flow_id: @flow.id, phase: 'phase1')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context 'some handlers' do
|
48
|
+
before do
|
49
|
+
@handlers = [
|
50
|
+
Distribot::PhaseHandler.new( name: 'FooHandler' ),
|
51
|
+
Distribot::PhaseHandler.new(
|
52
|
+
name: 'BarHandler',
|
53
|
+
version: '>= 1.0'
|
54
|
+
)
|
55
|
+
]
|
56
|
+
expect(Distribot).to receive(:subscribe)
|
57
|
+
@worker = described_class.new
|
58
|
+
end
|
59
|
+
context 'and all the handlers have suitable version matches' do
|
60
|
+
before do
|
61
|
+
expect(@worker).to receive(:best_version).ordered.with(@handlers[0]){ '1.0' }
|
62
|
+
expect(@worker).to receive(:best_version).ordered.with(@handlers[1]){ '2.0' }
|
63
|
+
expect(@worker).to receive(:init_handler).ordered.with(
|
64
|
+
@flow,
|
65
|
+
@phase,
|
66
|
+
@handlers[0],
|
67
|
+
'1.0'
|
68
|
+
)
|
69
|
+
expect(@worker).to receive(:init_handler).ordered.with(
|
70
|
+
@flow,
|
71
|
+
@phase,
|
72
|
+
@handlers[1],
|
73
|
+
'2.0'
|
74
|
+
)
|
75
|
+
end
|
76
|
+
it 'jumpstarts each handler' do
|
77
|
+
@worker.callback(flow_id: @flow.id, phase: 'phase1')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context 'any of the handlers cannot find a suitable version' do
|
81
|
+
before do
|
82
|
+
expect(@worker).to receive(:best_version)
|
83
|
+
expect(@worker).not_to receive(:init_handler)
|
84
|
+
end
|
85
|
+
it 'raises an exception' do
|
86
|
+
expect{@worker.callback(flow_id: @flow.id, phase: 'phase1')}.to raise_error RuntimeError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#best_version(handler)' do
|
94
|
+
before do
|
95
|
+
expect(Distribot).to receive(:connector) do
|
96
|
+
connector = double('connector')
|
97
|
+
expect(connector).to receive(:queues) {
|
98
|
+
%w(
|
99
|
+
distribot.flow.handler.FooHandler.0.9.0.tasks
|
100
|
+
distribot.flow.handler.FooHandler.1.0.0.tasks
|
101
|
+
distribot.flow.handler.FooHandler.1.0.1.tasks
|
102
|
+
distribot.flow.handler.FooHandler.2.0.0.tasks
|
103
|
+
distribot.flow.handler.BarHandler.1.0.0.tasks
|
104
|
+
)
|
105
|
+
}
|
106
|
+
connector
|
107
|
+
end
|
108
|
+
expect(Distribot).to receive(:subscribe)
|
109
|
+
@worker = described_class.new
|
110
|
+
end
|
111
|
+
context 'when the handler version' do
|
112
|
+
context 'is specified in the flow ' do
|
113
|
+
before do
|
114
|
+
@handler = Distribot::PhaseHandler.new(name: 'FooHandler', version: '~> 1.0')
|
115
|
+
end
|
116
|
+
it 'returns the highest available *matching* version for that handler' do
|
117
|
+
expect(@worker.best_version(@handler)).to eq '1.0.1'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
context 'is not specified in the flow' do
|
121
|
+
before do
|
122
|
+
@handler = Distribot::PhaseHandler.new(name: 'FooHandler')
|
123
|
+
end
|
124
|
+
it 'returns the highest available version for that handler' do
|
125
|
+
expect(@worker.best_version(@handler)).to eq '2.0.0'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#init_handler(flow, phase, handler, version)' do
|
132
|
+
before do
|
133
|
+
@flow = Distribot::Flow.new(id: 'xxx')
|
134
|
+
@phase = Distribot::Phase.new(name: 'phase1')
|
135
|
+
@handler = Distribot::PhaseHandler.new(name: 'FooHandler', version: '1.0')
|
136
|
+
@version = '1.0'
|
137
|
+
expect(Distribot).to receive(:subscribe)
|
138
|
+
expect(Distribot).to receive(:publish!).with("distribot.flow.handler.#{@handler}.#{@version}.enumerate",
|
139
|
+
flow_id: @flow.id,
|
140
|
+
phase: @phase.name,
|
141
|
+
task_queue: a_string_matching(/\.#{@handler}\.#{@version}\.tasks/),
|
142
|
+
task_counter: a_string_matching(/\.#{@flow.id}\.#{@phase.name}\.#{@handler}\.finished/),
|
143
|
+
finished_queue: 'distribot.flow.task.finished',
|
144
|
+
)
|
145
|
+
end
|
146
|
+
it 'publishes a message for the handler with everything it needs to begin task enumeration' do
|
147
|
+
described_class.new.init_handler(@flow, @phase, @handler, @version)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::Subscription do
|
4
|
+
describe 'inheritance' do
|
5
|
+
let(:subject){ described_class.new(nil) }
|
6
|
+
it { should be_a Distribot::ConnectionSharer }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#start(topic, options={}, &block)' do
|
10
|
+
it 'subscribes to the rabbit queue' do
|
11
|
+
# Arrange:
|
12
|
+
@topic = 'my.topic'
|
13
|
+
subscription = described_class.new(nil)
|
14
|
+
|
15
|
+
channel = double('channel')
|
16
|
+
expect(channel).to receive(:queue).ordered.with(@topic, anything) do
|
17
|
+
queue = double('queue')
|
18
|
+
expect(queue).to receive(:subscribe).with(hash_including(manual_ack: true)) do |args, &block|
|
19
|
+
# Send a good message:
|
20
|
+
delivery_info1 = OpenStruct.new(delivery_tag: 'tag1')
|
21
|
+
delivery_info2 = OpenStruct.new(delivery_tag: 'tag2')
|
22
|
+
block.call(delivery_info1, nil, {id: :good_message}.to_json )
|
23
|
+
block.call(delivery_info2, nil, {id: :bad_message}.to_json )
|
24
|
+
end
|
25
|
+
queue
|
26
|
+
end
|
27
|
+
expect(channel).to receive(:acknowledge).with('tag1', false)
|
28
|
+
expect(channel).to receive(:basic_reject).with('tag2', true)
|
29
|
+
|
30
|
+
expect(subscription).to receive(:channel).exactly(3).times{ channel }
|
31
|
+
|
32
|
+
# Act:
|
33
|
+
subscription.start(@topic) do |msg|
|
34
|
+
raise "Test error" if msg[:id] == 'bad_message'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Assert:
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Distribot::TaskFinishedHandler do
|
4
|
+
before do
|
5
|
+
Distribot.stub(:subscribe)
|
6
|
+
end
|
7
|
+
describe 'definition' do
|
8
|
+
it 'subscribes to the correct queue' do
|
9
|
+
expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.task.finished'
|
10
|
+
end
|
11
|
+
it 'declares a valid handler' do
|
12
|
+
expect(Distribot::Handler.handler_for(described_class)).to eq :callback
|
13
|
+
end
|
14
|
+
it 'has a method matching the handler name' do
|
15
|
+
expect(described_class.new).to respond_to :callback
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#callback(message)' do
|
20
|
+
before do
|
21
|
+
@message = {
|
22
|
+
task_queue: 'task-queue',
|
23
|
+
finished_queue: 'finished-queue',
|
24
|
+
phase: 'the-phase',
|
25
|
+
handler: 'the-hander',
|
26
|
+
flow_id: 'flow-id'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
context 'when the redis task counter' do
|
30
|
+
context 'is nil' do
|
31
|
+
before do
|
32
|
+
@redis = double('redis')
|
33
|
+
expect(@redis).to receive(:get){ nil }
|
34
|
+
expect(Distribot).to receive(:redis) do
|
35
|
+
@redis
|
36
|
+
end
|
37
|
+
end
|
38
|
+
it 'does nothing, because the handler is not yet finished' do
|
39
|
+
# Finally, action:
|
40
|
+
handler = Distribot::TaskFinishedHandler.new
|
41
|
+
handler.callback(@message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
context 'is not nil' do
|
45
|
+
before do
|
46
|
+
@redis = double('redis')
|
47
|
+
expect(Distribot).to receive(:redis).at_least(1).times do
|
48
|
+
@redis
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context 'when the task count after decrementing' do
|
52
|
+
context 'is == 0' do
|
53
|
+
before do
|
54
|
+
expect(@redis).to receive(:get){ 0 }
|
55
|
+
expect(@redis).to receive(:del).ordered
|
56
|
+
end
|
57
|
+
it 'publishes a message to the handler finished queue' do
|
58
|
+
handler = Distribot::TaskFinishedHandler.new
|
59
|
+
|
60
|
+
expect(Distribot).to receive(:publish!).with("distribot.flow.handler.finished", @message.except(:finished_queue))
|
61
|
+
|
62
|
+
# Finally, action:
|
63
|
+
handler.callback(@message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|