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.
- 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
|
+
|