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.
- data/CHANGELOG.txt +5 -0
- data/CREDITS.txt +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/Rakefile +30 -28
- data/TODO.txt +8 -0
- data/lib/ruote/amqp/alert_participant.rb +146 -0
- data/lib/ruote/amqp/participant.rb +350 -0
- data/lib/ruote/amqp/receiver.rb +181 -0
- data/lib/ruote/amqp/version.rb +28 -0
- data/lib/ruote/amqp.rb +68 -0
- data/lib/ruote-amqp.rb +1 -78
- data/ruote-amqp.gemspec +9 -6
- data/spec/alert_participant_spec.rb +58 -0
- data/spec/participant_spec.rb +268 -122
- data/spec/participant_subclass_spec.rb +66 -0
- data/spec/receiver_spec.rb +187 -79
- data/spec/spec_helper.rb +12 -70
- data/spec/support/ruote_amqp_helper.rb +40 -0
- metadata +28 -34
- data/PostInstall.txt +0 -12
- data/README.rdoc +0 -91
- data/lib/ruote-amqp/launchitem_listener.rb +0 -22
- data/lib/ruote-amqp/participant.rb +0 -241
- data/lib/ruote-amqp/receiver.rb +0 -147
- data/lib/ruote-amqp/version.rb +0 -6
- data/lib/ruote-amqp/workitem_listener.rb +0 -12
- data/spec/launchitem_listener_spec.rb +0 -70
- data/spec/ruote_amqp_spec.rb +0 -18
- data/spec/support/ruote_helpers.rb +0 -29
- data/spec/support/ruote_matchers.rb +0 -51
- data/spec/workitem_listener_spec.rb +0 -66
data/CHANGELOG.txt
CHANGED
data/CREDITS.txt
CHANGED
@@ -12,6 +12,7 @@
|
|
12
12
|
|
13
13
|
== CONTRIBUTORS
|
14
14
|
|
15
|
+
* Jiří Kubíček - https://github.com/kubicek
|
15
16
|
* Mario Camou
|
16
17
|
* Sean Johnson - https://github.com/belucid
|
17
18
|
* Victor Liu - https://github.com/pennymax
|
@@ -27,3 +28,6 @@
|
|
27
28
|
|
28
29
|
== FEEDBACK
|
29
30
|
|
31
|
+
* Jim Li - https://github.com/marsbomber - auto_recovery and co
|
32
|
+
* Marco Sehrer - https://github.com/pixelvitamina - ruote 2.2 vs 2.3
|
33
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2010-2012 Kenneth Kalmer
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
|
2
|
+
# ruote-amqp
|
3
|
+
|
4
|
+
ruote-amqp is a set of classes that let a ruote engine publish and/or receive messages over AMQP.
|
5
|
+
|
6
|
+
The most common use case is publishing workitems for processing by AMQP consumers and eventually receiving them back to resume the flow.
|
7
|
+
|
8
|
+
Another use case would be to listen on an AMQP queue for workflow launch requests.
|
9
|
+
|
10
|
+
Listening for arbitrary AMQP messages before resuming a flow (ambush/alert) is also possible.
|
11
|
+
|
12
|
+
|
13
|
+
## usage
|
14
|
+
|
15
|
+
### Ruote::Amqp::Participant
|
16
|
+
|
17
|
+
Publishing messages
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
$dashboard.register(
|
21
|
+
:toto,
|
22
|
+
Ruote::Amqp::Participant,
|
23
|
+
:exchange => [ 'direct', '' ],
|
24
|
+
:routing_key => 'alpha')
|
25
|
+
|
26
|
+
pdef = Ruote.define do
|
27
|
+
toto
|
28
|
+
end
|
29
|
+
|
30
|
+
$dashboard.launch(pdef)
|
31
|
+
|
32
|
+
# ...
|
33
|
+
```
|
34
|
+
|
35
|
+
### Ruote::Amqp::AlertParticipant
|
36
|
+
|
37
|
+
Ambushing messages from a process definition. The alert participant when
|
38
|
+
receiving a workitem starts waiting for the next message on a given queue. When
|
39
|
+
the message arrives, it responds to the engine (with a
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
$dashboard.register(
|
43
|
+
:wait_for_info,
|
44
|
+
Ruote::Amqp::AlertParticipant,
|
45
|
+
:queue => 'info')
|
46
|
+
|
47
|
+
pdef = Ruote.define do
|
48
|
+
# ... before
|
49
|
+
wait_for_job # flows wait for first message on 'info' queue
|
50
|
+
# ... after
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
By default it waits for 1 message that it places in the "amqp_message" field
|
55
|
+
of the workitem going back to the engine.
|
56
|
+
|
57
|
+
One can override the #handle method to change the way the workitem is modified
|
58
|
+
according to the message.
|
59
|
+
|
60
|
+
It's also OK to override the #on_workitem method of this participant if one
|
61
|
+
waits to wait for more than 1 message.
|
62
|
+
|
63
|
+
See the AlertParticipant rdoc for more.
|
64
|
+
|
65
|
+
### Ruote::Amqp::Receiver
|
66
|
+
|
67
|
+
Receiving messages.
|
68
|
+
|
69
|
+
A receiver is a ruote service subscribed to a queue. When a message comes on
|
70
|
+
the queue, the receiver will look at it and, according to the payload, either
|
71
|
+
launch a new workflow instance, resume a currently workflow instance segment
|
72
|
+
or pass an error back from a participant to the engine.
|
73
|
+
|
74
|
+
(In fact the resume workflow / pass participant error back to the engine are
|
75
|
+
closely related)
|
76
|
+
|
77
|
+
(NTS: at some point, receivers should be able to deal with "cancel messages")
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# A simple coupling between participant "toto" and a receiver via AMQP
|
81
|
+
#
|
82
|
+
# A real world example would have toto publishing somewhere, the message
|
83
|
+
# getting fetched by the real (remote) participant and then handed back
|
84
|
+
# on the queue the receiver is subscribed to.
|
85
|
+
|
86
|
+
$dashboard.register(
|
87
|
+
:toto,
|
88
|
+
Ruote::Amqp::Participant,
|
89
|
+
:exchange => [ 'direct', '' ],
|
90
|
+
:routing_key => 'alpha')
|
91
|
+
|
92
|
+
receiver = Ruote::Amqp::Receiver.new(
|
93
|
+
$dashboard, AMQP::Channel.new.queue('alpha'))
|
94
|
+
|
95
|
+
# ...
|
96
|
+
```
|
97
|
+
|
98
|
+
### Controlling the connection (AMQP session)
|
99
|
+
|
100
|
+
The Ruote::Amqp module has a handy singleton for connections (actually
|
101
|
+
AMQP::Session instances).
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# (before registering participants)
|
105
|
+
|
106
|
+
Ruote::Amqp.session = AMQP.connect(:auto_recovery => true) do |con|
|
107
|
+
con.on_recovery do |con|
|
108
|
+
puts "Recovered..."
|
109
|
+
end
|
110
|
+
connection.on_tcp_connection_loss do |con, settings|
|
111
|
+
puts "Reconnecting... please wait"
|
112
|
+
conn.reconnect(false, 20)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
When a participant tries to connect to AMQP, it will automatically use the value in Ruote::Amqp.session (else it will set up a new connection).
|
118
|
+
|
119
|
+
The receivers expect a queue when they are set up, feel free to set Ruote::Amqp.session, then use it when instantiating receivers (the participant will follow suit).
|
120
|
+
|
121
|
+
If you want a different way of connecting to AMQP for the participants, you can override their #amqp_connect methods (or pass them AMQP connection settings when registering them).
|
122
|
+
|
123
|
+
|
124
|
+
## requirements
|
125
|
+
|
126
|
+
* ruote[http://ruote.rubyforge.org] 2.3.0 or later
|
127
|
+
* amqp[http://rubyamqp.info/] 0.9.0 or later
|
128
|
+
* rabbitmq[http://www.rabbitmq.com/] 2.2.0 or later
|
129
|
+
|
130
|
+
|
131
|
+
## install
|
132
|
+
|
133
|
+
Please be sure to have read the requirements section above
|
134
|
+
|
135
|
+
gem install ruote-amqp
|
136
|
+
|
137
|
+
or via your Gemfile (thanks [bundler](http://gembundler.com)).
|
138
|
+
|
139
|
+
|
140
|
+
## tests / specs
|
141
|
+
|
142
|
+
To run the tests you need the following requirements met, or the testing environment will fail horribly (or simply get stuck without output).
|
143
|
+
|
144
|
+
|
145
|
+
### RabbitMQ vhost
|
146
|
+
|
147
|
+
The tests use dedicated vhost on a running AMQP broker. To configure RabbitMQ
|
148
|
+
you can run the following commands (the RabbitMQ server must be running):
|
149
|
+
|
150
|
+
$ rabbitmqctl add_vhost ruote-test
|
151
|
+
$ rabbitmqctl add_user ruote ruote
|
152
|
+
$ rabbitmqctl set_permissions -p ruote-test ruote '.*' '.*' '.*'
|
153
|
+
|
154
|
+
or by running:
|
155
|
+
|
156
|
+
$ rake prepare
|
157
|
+
|
158
|
+
|
159
|
+
If you need to change the AMQP configuration used by the tests, edit the
|
160
|
+
+spec/spec_helper.rb+ file.
|
161
|
+
|
162
|
+
|
163
|
+
## daemon-kit
|
164
|
+
|
165
|
+
Kenneth Kalmer, the original author of the ruote-amqp gem is also the author of [DaemonKit](https://github.com/kennethkalmer/daemon-kit) a library/toolbox for building daemons.
|
166
|
+
|
167
|
+
It used to be the preferred way to wrap remote participants (as daemons) but lately Kenneth hasn't had much time for support. It's still full of excellent ideas.
|
168
|
+
|
169
|
+
|
170
|
+
## license
|
171
|
+
|
172
|
+
MIT, see LICENSE.txt
|
173
|
+
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'rubygems/user_interaction' if Gem::RubyGemsVersion == '1.5.0'
|
|
6
6
|
|
7
7
|
require 'rake'
|
8
8
|
require 'rake/clean'
|
9
|
-
require 'rake/rdoctask'
|
9
|
+
#require 'rake/rdoctask'
|
10
10
|
|
11
11
|
|
12
12
|
#
|
@@ -68,34 +68,36 @@ task :prepare do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
|
71
|
+
##
|
72
|
+
## rdoc
|
73
|
+
##
|
74
|
+
## make sure to have rdoc 2.5.x to run that
|
71
75
|
#
|
72
|
-
#
|
76
|
+
#Rake::RDocTask.new do |rd|
|
73
77
|
#
|
74
|
-
#
|
75
|
-
|
76
|
-
Rake::RDocTask.new do |rd|
|
77
|
-
|
78
|
-
rd.main = 'README.rdoc'
|
79
|
-
rd.rdoc_dir = 'rdoc'
|
80
|
-
|
81
|
-
rd.rdoc_files.include(
|
82
|
-
'README.rdoc', 'CHANGELOG.txt', 'CREDITS.txt', 'lib/**/*.rb')
|
83
|
-
|
84
|
-
rd.title = "#{GEMSPEC.name} #{GEMSPEC.version}"
|
85
|
-
end
|
86
|
-
|
87
|
-
|
78
|
+
# rd.main = 'README.rdoc'
|
79
|
+
# rd.rdoc_dir = 'rdoc'
|
88
80
|
#
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
81
|
+
# rd.rdoc_files.include(
|
82
|
+
# 'README.rdoc', 'CHANGELOG.txt', 'CREDITS.txt', 'lib/**/*.rb')
|
83
|
+
#
|
84
|
+
# rd.title = "#{GEMSPEC.name} #{GEMSPEC.version}"
|
85
|
+
#end
|
86
|
+
#
|
87
|
+
#
|
88
|
+
##
|
89
|
+
## upload_rdoc
|
90
|
+
#
|
91
|
+
#desc %{
|
92
|
+
# upload the rdoc to rubyforge
|
93
|
+
#}
|
94
|
+
#task :upload_rdoc => [ :clean, :rdoc ] do
|
95
|
+
#
|
96
|
+
# account = 'jmettraux@rubyforge.org'
|
97
|
+
# webdir = '/var/www/gforge-projects/ruote'
|
98
|
+
#
|
99
|
+
# sh "rsync -azv -e ssh rdoc/#{GEMSPEC.name}_rdoc #{account}:#{webdir}/"
|
100
|
+
#end
|
101
|
+
#
|
102
|
+
# leverarge rdoc.info instead
|
101
103
|
|
data/TODO.txt
CHANGED
@@ -6,3 +6,11 @@
|
|
6
6
|
[ ] have a class method ParticipantProxy.stop_all ?
|
7
7
|
[ ] use Ruote::Workitem #as_json and #from_json(s)
|
8
8
|
|
9
|
+
*** 2.3.0
|
10
|
+
|
11
|
+
[x] https://gist.github.com/1944228
|
12
|
+
[ ] ON_CANCEL !
|
13
|
+
|
14
|
+
[ ] incorporate
|
15
|
+
https://github.com/marsbomber/ruote-amqp/commit/0f36a41f4a0254847a7b9a7c4b2098c8164f21f3
|
16
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#++
|
22
|
+
|
23
|
+
|
24
|
+
module Ruote::Amqp
|
25
|
+
|
26
|
+
#
|
27
|
+
# The alert participant, when invoked from a process instance will
|
28
|
+
# lay in wait for the next message on a given queue. As soon as the message
|
29
|
+
# comes in, it will pack it in the workitem fields and let the process
|
30
|
+
# definition resume.
|
31
|
+
#
|
32
|
+
# @dashboard.register(
|
33
|
+
# :wait_for_info,
|
34
|
+
# Ruote::Amqp::AlertParticipant,
|
35
|
+
# :queue => 'info')
|
36
|
+
#
|
37
|
+
# pdef = Ruote.define do
|
38
|
+
# wait_for_job
|
39
|
+
# # ... the rest of the flow
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# == configuration
|
43
|
+
#
|
44
|
+
# This class is mostly a subclass of Ruote::Amqp::Participant, it accepts
|
45
|
+
# the same configuration options (but has no need for 'exchange'). It
|
46
|
+
# accepts a 'queue' option in the form [ 'queue_name', { queue options } ].
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# == overriding #handle(header, payload)
|
50
|
+
#
|
51
|
+
# The default implementation for this method is:
|
52
|
+
#
|
53
|
+
# def handle(header, payload)
|
54
|
+
# workitem.fields['amqp_message'] = [ header.to_hash, payload ]
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# One is free to override it:
|
58
|
+
#
|
59
|
+
# class MyAlertParticipant < Ruote::Amqp::AlertParticipant
|
60
|
+
# def handle(header, payload)
|
61
|
+
# fields = Rufus::Json.decode(payload)
|
62
|
+
# workitem.fields.merge!(fields)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
#
|
67
|
+
# == overriding #on_workitem
|
68
|
+
#
|
69
|
+
# Out of the box, the alert participant listens for 1 message on 1 queue.
|
70
|
+
# It's not too difficult to change that.
|
71
|
+
#
|
72
|
+
# Resuming after 3 messages:
|
73
|
+
#
|
74
|
+
# class MyAlertParticipant < Ruote::Amqp::AlertParticipant
|
75
|
+
#
|
76
|
+
# def on_workitem
|
77
|
+
#
|
78
|
+
# messages = []
|
79
|
+
#
|
80
|
+
# queue.subscribe { |header, payload |
|
81
|
+
#
|
82
|
+
# messages << payload
|
83
|
+
#
|
84
|
+
# if messages.size > 2
|
85
|
+
# queue.unsubscribe
|
86
|
+
# workitem.fields['messages'] = messages
|
87
|
+
# reply # let the flow resume
|
88
|
+
# end
|
89
|
+
# }
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Observing 2 queues:
|
94
|
+
#
|
95
|
+
# class MyAlertParticipant < Ruote::Amqp::AlertParticipant
|
96
|
+
#
|
97
|
+
# def on_workitem
|
98
|
+
#
|
99
|
+
# messages = []
|
100
|
+
#
|
101
|
+
# q0 = channel.queue('zero')
|
102
|
+
# q1 = channel.queue('one')
|
103
|
+
#
|
104
|
+
# [ q0, q1 ].subscribe { |header, payload |
|
105
|
+
# messages << payload
|
106
|
+
# }
|
107
|
+
#
|
108
|
+
# sleep 1.0 while messages.size < 2
|
109
|
+
#
|
110
|
+
# reply # let the flow resume
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
class AlertParticipant < Participant
|
115
|
+
|
116
|
+
def on_workitem
|
117
|
+
|
118
|
+
queue.subscribe { |header, payload|
|
119
|
+
|
120
|
+
queue.unsubscribe
|
121
|
+
handle(header, payload)
|
122
|
+
reply
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
# Called when the AMQP message comes in. This default implementation
|
129
|
+
# stuffs the AMQP [ header, payload ] into an 'amqp_message' workitem
|
130
|
+
# field.
|
131
|
+
#
|
132
|
+
def handle(header, payload)
|
133
|
+
|
134
|
+
workitem.fields['amqp_message'] = [ header.to_hash, payload ]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Looks at the configuration options ('connection' and 'queue') and
|
138
|
+
# returns the queue the participant will fetch a message from.
|
139
|
+
#
|
140
|
+
def queue
|
141
|
+
|
142
|
+
@queue ||= channel.queue(*(opt('queue') || [ '' ]))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|