ruote-amqp 2.2.0 → 2.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.
@@ -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
-