ruote-amqp 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +0,0 @@
1
-
2
- module RuoteAMQP
3
-
4
- #
5
- # Got replaced by RuoteAMQP::Receiver
6
- #
7
- # This class is kept for backward compatibility.
8
- #
9
- class LaunchitemListener < ::RuoteAMQP::Receiver
10
-
11
- # Start a new LaunchItem listener
12
- #
13
- # @param [ Ruote::Engine, Ruote::Storage ] A configured ruote engine or storage instance
14
- # @param opts :queue / :unsubscribe
15
- #
16
- def initialize(engine_or_storage, opts={})
17
-
18
- super(engine_or_storage, opts.merge(:launchitems => :only))
19
- end
20
- end
21
- end
22
-
@@ -1,241 +0,0 @@
1
-
2
- require 'ruote/part/local_participant'
3
- require 'ruote-amqp'
4
-
5
-
6
- module RuoteAMQP
7
-
8
- #
9
- # = AMQP Participants
10
- #
11
- # The RuoteAMQP::ParticipantProxy allows you to send workitems (serialized as
12
- # JSON) or messages to any AMQP queues right from the process
13
- # definition. When combined with the RuoteAMQP::Receiver you can easily
14
- # leverage an extremely powerful local/remote participant
15
- # combinations.
16
- #
17
- # For local/remote participants The local part of the
18
- # RuoteAMQP::ParticipantProxy relies on the presence of a
19
- # RuoteAMQP::Receiver. Workitems are sent to the remote participant
20
- # and the local part does not normally reply to the engine. Instead
21
- # the engine will continue when a reply is received on the
22
- # 'ruote_workitems' queue (see RuoteAMQP::Receiver).
23
- #
24
- # Of course, the standard :forget => true format can be used even
25
- # with remote particpants and :forget can even be set as a default in
26
- # the options.
27
- #
28
- # A simple way to create a remote participant to act upon workitems
29
- # is to use the daemon-kit ruote responder.
30
- #
31
- # Simple AMQP messages are treated as 'fire and forget' and the flow
32
- # will continue when the local participant has queued the message
33
- # for sending. (As there is no meaningful way to receive a workitem
34
- # in reply).
35
- #
36
- # == Configuration
37
- #
38
- # AMQP configuration is handled by directly manipulating the
39
- # values of the +AMQP.settings+ hash, as provided by the AMQP
40
- # gem. No AMQP defaults are set by the participant.
41
- #
42
- # == Usage
43
- #
44
- # Define the queue used by an AMQP participant :
45
- #
46
- # engine.register_participant(
47
- # :delete_user, RuoteAMQP::ParticipantProxy, 'queue' => 'user_manager')
48
- #
49
- # Sending a workitem to the remote participant defined above:
50
- #
51
- # Ruote.process_definition do
52
- # sequence do
53
- # delete_user
54
- # end
55
- # end
56
- #
57
- # Let the local participant reply to the engine without involving
58
- # the receiver
59
- #
60
- # Ruote.process_definition do
61
- # sequence do
62
- # delete_user :forget => true
63
- # end
64
- # end
65
- #
66
- # Setting up the participant in a slightly more 'raw' way:
67
- #
68
- # engine.register_participant(
69
- # :amqp, RuoteAMQP::ParticipantProxy )
70
- #
71
- # Sending a workitem to a specific queue:
72
- #
73
- # Ruote.process_definition do
74
- # sequence do
75
- # amqp :queue => 'test', 'command' => '/run/regression_test'
76
- # end
77
- # end
78
- #
79
- # Setup a 'fire and forget' participant that always replies to the
80
- # engine:
81
- #
82
- # engine.register_participant(
83
- # :jfdi, RuoteAMQP::ParticipantProxy, 'forget' => true )
84
- #
85
- # Sending a message example to a specific queue (both steps are
86
- # equivalent):
87
- #
88
- # Ruote.process_definition do
89
- # sequence do
90
- # amqp :queue => 'test', :message => 'foo'
91
- # amqp :queue => 'test', :message => 'foo', :forget => true
92
- # end
93
- # end
94
- #
95
- #
96
- # == AMQP notes
97
- #
98
- # The participant currently only makes use of direct
99
- # exchanges. Possible future improvements might see use for topic
100
- # and fanout exchanges as well.
101
- #
102
- # The direct exchanges are always marked as durable by the
103
- # participant, and messages are marked as persistent by default (see
104
- # #RuoteAMQP)
105
- #
106
- class ParticipantProxy
107
-
108
- include Ruote::LocalParticipant
109
-
110
- # The following parameters are used in the process definition.
111
- #
112
- # An options hash with the same keys to provide defaults is
113
- # accepted at registration time (see above).
114
- #
115
- # * :queue => (string) The AMQP queue used by the remote participant.
116
- # nil by default.
117
- # * :forget => (bool) Whether the flow should block until the remote
118
- # participant replies.
119
- # false by default
120
- #
121
- def initialize(options)
122
-
123
- RuoteAMQP.start!
124
-
125
- @options = {
126
- 'queue' => nil,
127
- 'forget' => false,
128
- }.merge(options.inject({}) { |h, (k, v)|
129
- h[k.to_s] = v; h
130
- })
131
- #
132
- # the inject is here to make sure that all options have String keys
133
- end
134
-
135
- # Process the workitem at hand. By default the workitem will be
136
- # published to the direct exchange specified in the +queue+
137
- # workitem parameter. You can specify a +message+ workitem
138
- # parameter to have that sent instead of the workitem.
139
- #
140
- def consume(workitem)
141
-
142
- target_queue = determine_queue(workitem)
143
-
144
- raise 'no queue specified (outbound delivery)' unless target_queue
145
-
146
- q = MQ.queue(target_queue, :durable => true)
147
- forget = determine_forget(workitem)
148
-
149
- opts = {
150
- :persistent => RuoteAMQP.use_persistent_messages?,
151
- :content_type => 'application/json' }
152
-
153
- if message = workitem.fields['message'] || workitem.params['message']
154
-
155
- forget = true # sending a message implies 'forget' => true
156
-
157
- q.publish(message, opts)
158
-
159
- else
160
-
161
- q.publish(encode_workitem(workitem), opts)
162
- end
163
-
164
- reply_to_engine(workitem) if forget
165
- end
166
-
167
- # (Stops the underlying queue subscription)
168
- #
169
- def stop
170
-
171
- RuoteAMQP.stop!
172
- end
173
-
174
- def cancel(fei, flavour)
175
- #
176
- # TODO : sending a cancel item is not a bad idea, especially if the
177
- # job done over the amqp fence lasts...
178
- #
179
- end
180
-
181
- # The current AMQP (0.6.7) has 1 queue per thread. If you let the default
182
- # "one thread per participant consume call" kick in, you'll end up with
183
- # 1 queue per consume call (and...)
184
- #
185
- # So, by returning true here, we force the queue to be always the same.
186
- #
187
- # Many thanks to https://github.com/weifeng365 for reporting this issue
188
- # and suggesting the fix.
189
- #
190
- # TODO : should we have something to close queues when the engine / worker
191
- # shuts down ?
192
- # or is it already covered in the #stop ?
193
- #
194
- def do_not_thread
195
-
196
- true
197
- end
198
-
199
- private
200
-
201
- def determine_forget(workitem)
202
-
203
- return workitem.params['forget'] if workitem.params.has_key?('forget')
204
- return @options['forget'] if @options.has_key?('forget')
205
- false
206
- end
207
-
208
- def determine_queue(workitem)
209
-
210
- workitem.params['queue'] || @options['queue']
211
- end
212
-
213
- # Encodes the workitem as JSON. Makes sure to add to the field 'params'
214
- # an entry named 'participant_options' which contains the options of
215
- # this participant.
216
- #
217
- def encode_workitem(wi)
218
-
219
- wi.params['participant_options'] = @options
220
-
221
- Rufus::Json.encode(wi.to_h)
222
- end
223
- end
224
-
225
- #
226
- # Kept for backward compatibility.
227
- #
228
- # You should use RuoteAMQP::ParticipantProxy.
229
- #
230
- class Participant < ParticipantProxy
231
-
232
- def initialize(options)
233
- puts '=' * 80
234
- puts "RuoteAMQP::Participant will be deprecated soon (2.1.12)"
235
- puts "please use RuoteAMQP::ParticipantProxy instead"
236
- puts '=' * 80
237
- super
238
- end
239
- end
240
- end
241
-
@@ -1,147 +0,0 @@
1
-
2
- require 'ruote-amqp'
3
-
4
-
5
- module RuoteAMQP
6
-
7
- #
8
- # = AMQP Receiver
9
- #
10
- # Used in conjunction with the RuoteAMQP::Participant, the WorkitemListener
11
- # subscribes to a specific direct exchange and monitors for
12
- # incoming workitems. It expects workitems to arrive serialized as
13
- # JSON.
14
- #
15
- # == Configuration
16
- #
17
- # AMQP configuration is handled by directly manipulating the values of
18
- # the +AMQP.settings+ hash, as provided by the AMQP gem. No
19
- # defaults are set by the listener. The only +option+ parsed by
20
- # the initializer of the workitem listener is the +queue+ key (Hash
21
- # expected). If no +queue+ key is set, the listener will subscribe
22
- # to the +ruote_workitems+ direct exchange for workitems, otherwise it will
23
- # subscribe to the direct exchange provided.
24
- #
25
- # == Usage
26
- #
27
- # Register the engine or storage with the listener:
28
- #
29
- # RuoteAMQP::Receiver.new(engine_or_storage)
30
- #
31
- # The workitem listener leverages the asynchronous nature of the amqp gem,
32
- # so no timers are setup when initialized.
33
- #
34
- # == Options
35
- #
36
- # :queue and :launchitems
37
- #
38
- # See the RuoteAMQP::Participant docs for information on sending
39
- # workitems out to remote participants, and have them send replies
40
- # to the correct direct exchange specified in the workitem
41
- # attributes.
42
- #
43
- class Receiver < Ruote::Receiver
44
-
45
- attr_reader :queue
46
-
47
- # Starts a new Receiver
48
- #
49
- # Two arguments for this method.
50
- #
51
- # The first oone should be a Ruote::Engine, a Ruote::Storage or
52
- # a Ruote::Worker instance.
53
- #
54
- # The second one is a hash for options. There are two known options :
55
- #
56
- # :queue for setting the queue on which to listen (defaults to
57
- # 'ruote_workitems').
58
- #
59
- # The :launchitems option :
60
- #
61
- # :launchitems => true
62
- # # the receiver accepts workitems and launchitems
63
- # :launchitems => false
64
- # # the receiver only accepts workitems
65
- # :launchitems => :only
66
- # # the receiver only accepts launchitems
67
- #
68
- def initialize(engine_or_storage, opts={})
69
-
70
- super(engine_or_storage)
71
-
72
- @launchitems = opts[:launchitems]
73
-
74
- @queue =
75
- opts[:queue] ||
76
- (@launchitems == :only ? 'ruote_launchitems' : 'ruote_workitems')
77
-
78
- RuoteAMQP.start!
79
-
80
- if opts[:unsubscribe]
81
- MQ.queue(@queue, :durable => true).unsubscribe
82
- sleep 0.300
83
- end
84
-
85
- MQ.queue(@queue, :durable => true).subscribe do |message|
86
- if AMQP.closing?
87
- # do nothing, we're going down
88
- else
89
- handle(message)
90
- end
91
- end
92
- end
93
-
94
- def stop
95
-
96
- RuoteAMQP.stop!
97
- end
98
-
99
- # (feel free to overwrite me)
100
- #
101
- def decode_workitem(msg)
102
-
103
- (Rufus::Json.decode(msg) rescue nil)
104
- end
105
-
106
- private
107
-
108
- def handle(msg)
109
-
110
- item = decode_workitem(msg)
111
-
112
- return unless item.is_a?(Hash)
113
-
114
- not_li = ! item.has_key?('definition')
115
-
116
- return if @launchitems == :only && not_li
117
- return unless @launchitems || not_li
118
-
119
- if not_li
120
- receive(item) # workitem resumes in its process instance
121
- else
122
- launch(item) # new process instance launch
123
- end
124
-
125
- rescue => e
126
- # something went wrong
127
- # let's simply discard the message
128
- $stderr.puts('=' * 80)
129
- $stderr.puts(self.class.name)
130
- $stderr.puts("couldn't handle incoming message :")
131
- $stderr.puts('')
132
- $stderr.puts(msg.inspect)
133
- $stderr.puts('')
134
- $stderr.puts(Rufus::Json.pretty_encode(item)) rescue nil
135
- $stderr.puts('')
136
- $stderr.puts(e.inspect)
137
- $stderr.puts(e.backtrace)
138
- $stderr.puts('=' * 80)
139
- end
140
-
141
- def launch(hash)
142
-
143
- super(hash['definition'], hash['fields'] || {}, hash['variables'] || {})
144
- end
145
- end
146
- end
147
-
@@ -1,6 +0,0 @@
1
-
2
- module RuoteAMQP
3
-
4
- VERSION = '2.2.0'
5
- end
6
-
@@ -1,12 +0,0 @@
1
-
2
- module RuoteAMQP
3
-
4
- #
5
- # Got replaced by RuoteAMQP::Receiver
6
- #
7
- # This class is kept for backward compatibility.
8
- #
9
- class WorkitemListener < ::RuoteAMQP::Receiver
10
- end
11
- end
12
-
@@ -1,70 +0,0 @@
1
-
2
- require File.join(File.dirname(__FILE__), 'spec_helper')
3
-
4
- #
5
- # NOTE : RuoteAMQP::LaunchitemListener has been depreacted in favour of
6
- # RuoteAMQP::Receiver
7
- #
8
-
9
- describe RuoteAMQP::LaunchitemListener do
10
-
11
- after(:each) do
12
- purge_engine
13
- end
14
-
15
- it 'launches processes' do
16
-
17
- json = {
18
- 'definition' => %{
19
- Ruote.process_definition :name => 'test' do
20
- sequence do
21
- echo '${f:foo}'
22
- end
23
- end
24
- },
25
- 'fields' => { 'foo' => 'bar' }
26
- }.to_json
27
-
28
- RuoteAMQP::LaunchitemListener.new(@engine)
29
-
30
- MQ.queue('ruote_launchitems', :durable => true).publish(json)
31
-
32
- sleep 0.5
33
-
34
- @engine.should_not have_errors
35
- @engine.should_not have_remaining_expressions
36
-
37
- @tracer.to_s.should == 'bar'
38
- end
39
-
40
- it 'discards corrupt process definitions' do
41
-
42
- json = {
43
- 'definition' => %{
44
- I'm a broken process definition
45
- },
46
- 'fields' => { 'foo' => 'bar' }
47
- }.to_json
48
-
49
- RuoteAMQP::LaunchitemListener.new(@engine, :unsubscribe => true)
50
-
51
- serr = String.new
52
- err = StringIO.new(serr, 'w+')
53
- $stderr = err
54
-
55
- MQ.queue('ruote_launchitems', :durable => true).publish(json)
56
-
57
- sleep 0.5
58
-
59
- err.close
60
- $stderr = STDERR
61
-
62
- @engine.should_not have_errors
63
- @engine.should_not have_remaining_expressions
64
-
65
- @tracer.to_s.should == ''
66
-
67
- serr.should match(/^===/)
68
- end
69
- end
70
-
@@ -1,18 +0,0 @@
1
-
2
- require File.join(File.dirname(__FILE__), 'spec_helper')
3
-
4
-
5
- describe RuoteAMQP do
6
-
7
- it "uses persistent messages by default" do
8
-
9
- RuoteAMQP.use_persistent_messages?.should be_true
10
- end
11
-
12
- it "allows switching to transient messages" do
13
-
14
- RuoteAMQP.use_persistent_messages = false
15
- RuoteAMQP.use_persistent_messages?.should be_false
16
- end
17
- end
18
-
@@ -1,29 +0,0 @@
1
-
2
- module RuoteSpecHelpers
3
-
4
- def purge_engine
5
-
6
- # TODO : adapt to ruote 2.1.10
7
- end
8
-
9
- def run_definition(pdef)
10
-
11
- wfid = @engine.launch(pdef)
12
-
13
- #r = @engine.wait_for(wfid)
14
- #@engine.wait_for(wfid) if r['action'] == 'ceased'
15
- # # make sure to wait for 'terminated'
16
- @engine.wait_for(:inactive)
17
-
18
- @engine.should_not have_errors
19
- @engine.should_not have_remaining_expressions
20
-
21
- purge_engine
22
- end
23
-
24
- def noisy(on = true)
25
-
26
- @engine.context.logger.noisy = on
27
- end
28
- end
29
-
@@ -1,51 +0,0 @@
1
-
2
- RSpec::Matchers.define :have_errors do |*args|
3
-
4
- match do |engine|
5
-
6
- @ps = if wfid = args.shift
7
- engine.processes(wfid)
8
- else
9
- engine.processes.first
10
- end
11
-
12
- @ps ? (@ps.errors.size != 0) : false
13
- end
14
-
15
- failure_message_for_should do |engine|
16
-
17
- "Expected engine to have errors, but didn't"
18
- end
19
-
20
- failure_message_for_should_not do |engine|
21
-
22
- "Expected the engine to not have errors, but it did.\n" +
23
- @ps.errors.map { |e| " * error: #{e.message}\n\"#{e.trace}\"" }.join("\n")
24
- end
25
-
26
- description do
27
- end
28
- end
29
-
30
- RSpec::Matchers.define :have_remaining_expressions do
31
-
32
- match do |engine|
33
-
34
- (engine.storage.get_many('expressions').size != 0)
35
- end
36
-
37
- failure_message_for_should do |engine|
38
-
39
- "Expected engine to have processes remaining, but it didn't"
40
- end
41
-
42
- failure_message_for_should_not do |engine|
43
-
44
- "Expected engine to have no processes remaining, but it did." +
45
- "#{engine.storage.get_many('expressions').inspect}"
46
- end
47
-
48
- description do
49
- end
50
- end
51
-
@@ -1,66 +0,0 @@
1
-
2
- require File.join(File.dirname(__FILE__), 'spec_helper')
3
-
4
- #
5
- # NOTE : RuoteAMQP::WorkitemListener has been depreacted in favour of
6
- # RuoteAMQP::Receiver
7
- #
8
-
9
-
10
- describe RuoteAMQP::WorkitemListener do
11
-
12
- after(:each) do
13
- purge_engine
14
- end
15
-
16
- it "handles replies" do
17
-
18
- pdef = Ruote.process_definition :name => 'test' do
19
- set :field => 'foo', :value => 'foo'
20
- sequence do
21
- echo '${f:foo}'
22
- amqp :queue => 'test7'
23
- echo '${f:foo}'
24
- end
25
- end
26
-
27
- @engine.register_participant(:amqp, RuoteAMQP::ParticipantProxy)
28
-
29
- RuoteAMQP::WorkitemListener.new(@engine, :unsubscribe => true)
30
-
31
- #@engine.noisy = true
32
-
33
- wfid = @engine.launch(pdef)
34
-
35
- workitem = nil
36
-
37
- begin
38
- Timeout::timeout(5) do
39
-
40
- MQ.queue('test7', :durable => true).subscribe { |msg|
41
- wi = Ruote::Workitem.new(Rufus::Json.decode(msg))
42
- workitem = wi if wi.wfid == wfid
43
- }
44
-
45
- loop do
46
- break unless workitem.nil?
47
- sleep 0.1
48
- end
49
- end
50
- rescue Timeout::Error
51
- violated "Timeout waiting for message"
52
- end
53
-
54
- workitem.fields['foo'] = 'bar'
55
-
56
- MQ.queue('ruote_workitems', :durable => true).publish(Rufus::Json.encode(workitem.to_h), :persistent => true)
57
-
58
- @engine.wait_for(wfid)
59
-
60
- @engine.should_not have_errors
61
- @engine.should_not have_remaining_expressions
62
-
63
- @tracer.to_s.should == "foo\nbar"
64
- end
65
- end
66
-