simianarmy-ruote-amqp 0.9.21
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/History.txt +11 -0
- data/Manifest.txt +21 -0
- data/PostInstall.txt +12 -0
- data/README.rdoc +85 -0
- data/Rakefile +26 -0
- data/TODO.txt +4 -0
- data/lib/ruote-amqp.rb +25 -0
- data/lib/ruote-amqp/listener.rb +92 -0
- data/lib/ruote-amqp/participant.rb +195 -0
- data/lib/spec/ruote.rb +5 -0
- data/lib/spec/ruote_example_group.rb +8 -0
- data/lib/spec/ruote_helpers.rb +24 -0
- data/lib/spec/ruote_matchers.rb +56 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/listener_spec.rb +53 -0
- data/spec/participant_spec.rb +183 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +87 -0
- data/tasks/rspec.rake +21 -0
- metadata +114 -0
data/History.txt
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
=== 0.9.21 2009-07-13
|
2
|
+
|
3
|
+
* Depend on ruote-0.9.21 for flexible JSON backends
|
4
|
+
* Support for default queues
|
5
|
+
* Support for mapping participant names to queue names
|
6
|
+
* Plenty of RDOC fixed
|
7
|
+
|
8
|
+
=== 0.9.20 2009-07-13
|
9
|
+
|
10
|
+
* 1 major enhancement:
|
11
|
+
* Initial release
|
data/Manifest.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
TODO.txt
|
7
|
+
lib/ruote-amqp.rb
|
8
|
+
lib/ruote-amqp/listener.rb
|
9
|
+
lib/ruote-amqp/participant.rb
|
10
|
+
lib/spec/ruote.rb
|
11
|
+
lib/spec/ruote_example_group.rb
|
12
|
+
lib/spec/ruote_helpers.rb
|
13
|
+
lib/spec/ruote_matchers.rb
|
14
|
+
script/console
|
15
|
+
script/destroy
|
16
|
+
script/generate
|
17
|
+
spec/listener_spec.rb
|
18
|
+
spec/participant_spec.rb
|
19
|
+
spec/spec.opts
|
20
|
+
spec/spec_helper.rb
|
21
|
+
tasks/rspec.rake
|
data/PostInstall.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
For more information on ruote-amqp, see http://github.com/kennethkalmer/ruote-amqp
|
3
|
+
|
4
|
+
Please note that this gem requires access to an AMQP broker and a good
|
5
|
+
understanding of AMQP and remote participants in general.
|
6
|
+
|
7
|
+
Join us in #ruote on Freenode or on the openwfe-users Google Group to discuss
|
8
|
+
it's uses.
|
9
|
+
|
10
|
+
You might also want to look at daemon-kit for help with writing external
|
11
|
+
participants that communicate with ruote via AMQP.
|
12
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
= ruote-amqp
|
2
|
+
|
3
|
+
* http://github.com/kennethkalmer/ruote-amqp
|
4
|
+
* http://openwfe.rubyforge.org
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
ruote-amqp provides an AMQP participant/listener pair that allows you to
|
9
|
+
distribute workitems out to AMQP consumers for processing.
|
10
|
+
|
11
|
+
To learn more about remote participants in ruote please see
|
12
|
+
http://openwfe.rubyforge.org/part.html
|
13
|
+
|
14
|
+
== FEATURES/PROBLEMS:
|
15
|
+
|
16
|
+
* Flexible participant for sending workitems
|
17
|
+
* Flexible listener for receiving replies
|
18
|
+
* Fully evented (thanks to the amqp gem)
|
19
|
+
|
20
|
+
== SYNOPSIS:
|
21
|
+
|
22
|
+
Please review the detailed RDOC in RuoteAMQP::Participant and Ruote::AMQP::Listener
|
23
|
+
|
24
|
+
== REQUIREMENTS:
|
25
|
+
|
26
|
+
* ruote[http://openwfe.rubyforge.org] 0.9.21 or later
|
27
|
+
* amqp[http://github.com/tmm1/amqp] 0.6.1 or later
|
28
|
+
|
29
|
+
NOTE: It might be required that you build the amqp gem yourself.
|
30
|
+
|
31
|
+
ruote 0.9.21 is currently only available to build on your own since John
|
32
|
+
Mettraux is working tirelessly to ship ruote 2.0. To build your own ruote
|
33
|
+
0.9.21 gem run these commands:
|
34
|
+
|
35
|
+
$ git clone git://github.com/jmettraux/ruote.git
|
36
|
+
$ cd ruote
|
37
|
+
$ rake gem
|
38
|
+
$ sudo gem install pkg/ruote-0.9.21.gem
|
39
|
+
|
40
|
+
Please note that this requires Rubygems 1.3.2 or newer to work
|
41
|
+
|
42
|
+
If you're using ruote 0.9.20 and would like to use ruote-amqp, you can install
|
43
|
+
ruote-amqp-0.9.20, like so:
|
44
|
+
|
45
|
+
$ sudo gem install ruote-amqp -v 0.9.20
|
46
|
+
|
47
|
+
== INSTALL:
|
48
|
+
|
49
|
+
Please be sure to have read the requirements section above
|
50
|
+
|
51
|
+
* sudo gem install ruote-amqp
|
52
|
+
|
53
|
+
== DAEMON-KIT:
|
54
|
+
|
55
|
+
Although the RuoteAMQP gem will work perfectly well with any AMQP consumer,
|
56
|
+
it is recommended that you use daemon-kit[http://github.com/kennethkalmer/daemon-kit]
|
57
|
+
to write your remote participants.
|
58
|
+
|
59
|
+
daemon-kit offers plenty of convenience for remote participants and includes
|
60
|
+
a code generator for ruote remote participants.
|
61
|
+
|
62
|
+
== LICENSE:
|
63
|
+
|
64
|
+
(The MIT License)
|
65
|
+
|
66
|
+
Copyright (c) 2009 Kenneth Kalmer
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
'Software'), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
82
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
83
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
84
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
85
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.4.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/ruote-amqp'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'simianarmy-ruote-amqp' do
|
14
|
+
self.developer 'Kenneth Kalmer', 'kenneth.kalmer@gmail.com'
|
15
|
+
self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
16
|
+
self.rubyforge_name = self.name # TODO this is default value
|
17
|
+
self.extra_deps = [['ruote','= 0.9.21'], ['amqp', '= 0.6.7']]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
23
|
+
|
24
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
25
|
+
# remove_task :default
|
26
|
+
# task :default => [:spec, :features]
|
data/TODO.txt
ADDED
data/lib/ruote-amqp.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'openwfe'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'ruote', '>= 0.9.21'
|
9
|
+
require 'openwfe'
|
10
|
+
end
|
11
|
+
require 'openwfe/version'
|
12
|
+
|
13
|
+
if OpenWFE::OPENWFERU_VERSION < '0.9.21'
|
14
|
+
raise "ruote-amqp requires at least ruote-0.9.21"
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'yaml'
|
18
|
+
require 'mq'
|
19
|
+
|
20
|
+
module RuoteAMQP
|
21
|
+
VERSION = '0.9.21'
|
22
|
+
|
23
|
+
autoload 'Participant', 'ruote-amqp/participant'
|
24
|
+
autoload 'Listener', 'ruote-amqp/listener'
|
25
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'openwfe/service'
|
2
|
+
require 'openwfe/listeners/listener'
|
3
|
+
|
4
|
+
module RuoteAMQP
|
5
|
+
|
6
|
+
#
|
7
|
+
# = AMQP Listeners
|
8
|
+
#
|
9
|
+
# Used in conjunction with the RuoteAMQP::Participant, the Listener
|
10
|
+
# subscribes to a specific direct exchange and monitors for
|
11
|
+
# incoming workitems. It expects workitems to arrive serialized as
|
12
|
+
# JSON.
|
13
|
+
#
|
14
|
+
# == Configuration
|
15
|
+
#
|
16
|
+
# AMQP configuration is handled by directly manipulating the values of
|
17
|
+
# the +AMQP.settings+ hash, as provided by the AMQP gem. No
|
18
|
+
# defaults are set by the participant. The only +option+ parsed by
|
19
|
+
# the initializer of the listener is the +queue+ key (Hash
|
20
|
+
# expected). If no +queue+ key is set, the listener will subscribe
|
21
|
+
# to the +ruote+ direct exchange for workitems, otherwise it will
|
22
|
+
# subscribe to the direct exchange provided.
|
23
|
+
#
|
24
|
+
# The participant requires version 0.6.1 or later of the amqp gem.
|
25
|
+
#
|
26
|
+
# == Usage
|
27
|
+
#
|
28
|
+
# Register the listener with the engine:
|
29
|
+
#
|
30
|
+
# engine.register_listener( RuoteAMQP::Listener )
|
31
|
+
#
|
32
|
+
# The listener leverages the asynchronous nature of the amqp gem,
|
33
|
+
# so no timers are setup when initialized.
|
34
|
+
#
|
35
|
+
# See the RuoteAMQP::Participant docs for information on sending
|
36
|
+
# workitems out to remote participants, and have them send replies
|
37
|
+
# to the correct direct exchange specified in the workitem
|
38
|
+
# attributes.
|
39
|
+
#
|
40
|
+
class Listener < OpenWFE::Service
|
41
|
+
include OpenWFE::WorkItemListener
|
42
|
+
|
43
|
+
class << self
|
44
|
+
|
45
|
+
# Listening queue
|
46
|
+
attr_writer :queue
|
47
|
+
|
48
|
+
def queue
|
49
|
+
@queue ||= 'ruote'
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
# Only one option is used (:queue) to determine where to listen
|
55
|
+
# for work
|
56
|
+
def initialize( service_name, options )
|
57
|
+
|
58
|
+
if q = options.delete(:queue)
|
59
|
+
self.class.queue = q
|
60
|
+
end
|
61
|
+
|
62
|
+
super( service_name, options )
|
63
|
+
|
64
|
+
#if AMQP.connection.nil?
|
65
|
+
@em_thread = Thread.new { EM.run } unless EM.reactor_running?
|
66
|
+
#end
|
67
|
+
|
68
|
+
MQ.queue( self.class.queue, :durable => true ).subscribe do |message|
|
69
|
+
workitem = decode_workitem( message )
|
70
|
+
ldebug { "workitem from '#{self.class.queue}': #{workitem.inspect}" }
|
71
|
+
handle_item( workitem )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop
|
76
|
+
linfo { "Stopping..." }
|
77
|
+
|
78
|
+
AMQP.stop { EM.stop } #if EM.reactor_running? }
|
79
|
+
@em_thread.join if @em_thread
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Complicated guesswork that needs to happen here to detect the format
|
85
|
+
def decode_workitem( msg )
|
86
|
+
ldebug { "decoding workitem from: #{msg}" }
|
87
|
+
|
88
|
+
hash = OpenWFE::Json.decode(msg)
|
89
|
+
OpenWFE.workitem_from_h( hash )
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module RuoteAMQP
|
2
|
+
|
3
|
+
# = AMQP Participants
|
4
|
+
#
|
5
|
+
# The RuoteAMQP::Participant allows you to send workitems (serialized as
|
6
|
+
# JSON) or messages to any AMQP queues right from the process
|
7
|
+
# definition. When combined with the RuoteAMQP::Listener you can easily
|
8
|
+
# leverage an extremely powerful local/remote participant
|
9
|
+
# combinations.
|
10
|
+
#
|
11
|
+
# By default the participant relies on the presence of an AMQP
|
12
|
+
# listener. Workitems are sent and no replies are given to the
|
13
|
+
# engine. The participant can be configured to reply to the engine
|
14
|
+
# immediately after queueing a message, see the usage section below.
|
15
|
+
#
|
16
|
+
# == Configuration
|
17
|
+
#
|
18
|
+
# AMQP configuration is handled by directly manipulating the
|
19
|
+
# values of the +AMQP.settings+ hash, as provided by the AMQP
|
20
|
+
# gem. No AMQP defaults are set by the participant.
|
21
|
+
#
|
22
|
+
# The participant requires version 0.6.1 or later of the amqp gem.
|
23
|
+
#
|
24
|
+
# == Usage
|
25
|
+
#
|
26
|
+
# Currently it's possible to send either workitems or messages
|
27
|
+
# directly to a specific queue, and have the engine wait for
|
28
|
+
# replies on another queue (see AMQPListener).
|
29
|
+
#
|
30
|
+
# Setting up the participant
|
31
|
+
#
|
32
|
+
# engine.register_participant(
|
33
|
+
# :amqp, RuoteAMQP::Participant )
|
34
|
+
#
|
35
|
+
# Setup a participant that always replies to the engine
|
36
|
+
#
|
37
|
+
# engine.register_participant(
|
38
|
+
# :amqp, RuoteAMQP::Participant.new(:reply_by_default => true ) )
|
39
|
+
#
|
40
|
+
# Sending a message example
|
41
|
+
#
|
42
|
+
# class AmqpMessageExample0 < OpenWFE::ProcessDefinition
|
43
|
+
# sequence do
|
44
|
+
# amqp :queue => 'test', :message => 'foo'
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Sending a workitem
|
49
|
+
#
|
50
|
+
# class AmqpWorkitemExample0 < OpenWFE::ProcessDefinition
|
51
|
+
# sequence do
|
52
|
+
# amqp :queue => 'test'
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# Let the participant reply to the engine without involving the listener
|
57
|
+
#
|
58
|
+
# class AmqpWaitExample < OpenWFE::ProcessDefinition
|
59
|
+
# sequence do
|
60
|
+
# amqp :queue => 'test', :reply_anyway => true
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# When waiting for a reply it only makes sense to send a workitem.
|
65
|
+
#
|
66
|
+
# Keeping things DRY with participant name to queue maps:
|
67
|
+
#
|
68
|
+
# amqp = RuoteAMQP::Participant.new( :default_queue => 'test' )
|
69
|
+
# amqp.map_participant 'george', 'whitehouse'
|
70
|
+
# amqp.map_participant 'barak', 'whitehouse'
|
71
|
+
# amqp.map_participant 'greenspan', 'treasury'
|
72
|
+
#
|
73
|
+
# engine.register_participant( :george, amqp )
|
74
|
+
# engine.register_participant( :barak, amqp )
|
75
|
+
# engine.register_participant( :greespan, amqp )
|
76
|
+
# engine.register_participant( :amqp, amqp )
|
77
|
+
#
|
78
|
+
# class DryAmqProcess0 < OpenWFE::ProcessDefinition
|
79
|
+
# cursor :break_if => "${f:economy_recovered}" do
|
80
|
+
# # Workitem sent to 'whitehouse' queue
|
81
|
+
# george :activity => 'Tank economy'
|
82
|
+
#
|
83
|
+
# # Workitem sent to 'treasury' queue
|
84
|
+
# greenspan :activity => 'Resign'
|
85
|
+
#
|
86
|
+
# # Workitem sent to 'whitehouse' queue
|
87
|
+
# barak :activity => 'Cleanup mess'
|
88
|
+
#
|
89
|
+
# # Workitem sent to default 'test' queue
|
90
|
+
# amqp :activity => 'Notify CNN'
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# == Workitem modifications
|
95
|
+
#
|
96
|
+
# To ease replies, and additional workitem attribute is set:
|
97
|
+
#
|
98
|
+
# 'reply_queue'
|
99
|
+
#
|
100
|
+
# +reply_queue+ has the name of the queue where the RuoteAMQP::Listener
|
101
|
+
# expects replies from remote participants
|
102
|
+
#
|
103
|
+
# == AMQP notes
|
104
|
+
#
|
105
|
+
# The participant currently only makes use of direct
|
106
|
+
# exchanges. Possible future improvements might see use for topic
|
107
|
+
# and fanout exchanges as well.
|
108
|
+
#
|
109
|
+
# The direct exchanges are always marked as durable by the
|
110
|
+
# participant.
|
111
|
+
#
|
112
|
+
class Participant
|
113
|
+
include OpenWFE::LocalParticipant
|
114
|
+
|
115
|
+
# Accepts an options hash with the following keys:
|
116
|
+
#
|
117
|
+
# * :reply_by_default => (bool) false by default
|
118
|
+
# * :default_queue => (string) nil by default
|
119
|
+
def initialize( options = {} )
|
120
|
+
ensure_reactor!
|
121
|
+
|
122
|
+
@options = {
|
123
|
+
:reply_by_default => false,
|
124
|
+
:default_queue => nil
|
125
|
+
}.merge( options )
|
126
|
+
|
127
|
+
@participant_maps = {}
|
128
|
+
end
|
129
|
+
|
130
|
+
def map_participant( name, queue )
|
131
|
+
@participant_maps[ name ] = queue
|
132
|
+
end
|
133
|
+
|
134
|
+
# Process the workitem at hand. By default the workitem will be
|
135
|
+
# published to the direct exchange specified in the +queue+
|
136
|
+
# workitem parameter. You can specify a +message+ workitem
|
137
|
+
# parameter to have that sent instead of the workitem.
|
138
|
+
#
|
139
|
+
# To force the participant to reply to the engine, set the
|
140
|
+
# +reply_anyway+ workitem parameter.
|
141
|
+
def consume( workitem )
|
142
|
+
ldebug { "consuming workitem" }
|
143
|
+
ensure_reactor!
|
144
|
+
|
145
|
+
if target_queue = determine_queue( workitem )
|
146
|
+
|
147
|
+
q = MQ.queue( target_queue, :durable => true )
|
148
|
+
|
149
|
+
# Message or workitem?
|
150
|
+
if message = ( workitem.attributes['message'] || workitem.params['message'] )
|
151
|
+
ldebug { "sending message to queue: #{target_queue}" }
|
152
|
+
q.publish( message )
|
153
|
+
|
154
|
+
else
|
155
|
+
ldebug { "sending workitem to queue: #{target_queue}" }
|
156
|
+
|
157
|
+
q.publish( encode_workitem( workitem ) )
|
158
|
+
end
|
159
|
+
else
|
160
|
+
lerror { "no queue in workitem params!" }
|
161
|
+
end
|
162
|
+
|
163
|
+
if @options[:reply_by_default] || workitem.params['reply-anyway'] == true
|
164
|
+
reply_to_engine( workitem )
|
165
|
+
end
|
166
|
+
|
167
|
+
ldebug { "done" }
|
168
|
+
end
|
169
|
+
|
170
|
+
def stop
|
171
|
+
linfo { "Stopping..." }
|
172
|
+
|
173
|
+
AMQP.stop { EM.stop } #if EM.reactor_running? }
|
174
|
+
@em_thread.join if @em_thread
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def determine_queue( workitem )
|
180
|
+
workitem.params['queue'] ||
|
181
|
+
@participant_maps[ workitem.participant_name ] ||
|
182
|
+
@options[:default_queue]
|
183
|
+
end
|
184
|
+
|
185
|
+
# Encode (and extend) the workitem as JSON
|
186
|
+
def encode_workitem( wi )
|
187
|
+
wi.attributes['reply_queue'] = Listener.queue
|
188
|
+
OpenWFE::Json.encode( wi.to_h )
|
189
|
+
end
|
190
|
+
|
191
|
+
def ensure_reactor!
|
192
|
+
@em_thread = Thread.new { EM.run } unless EM.reactor_running?
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/spec/ruote.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module RuoteSpecHelpers
|
2
|
+
|
3
|
+
def purge_engine
|
4
|
+
@engine.application_context.values.each do |s|
|
5
|
+
s.purge if s.respond_to?(:purge)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def run_definition( pdef )
|
10
|
+
fei = @engine.launch( pdef )
|
11
|
+
wait( fei )
|
12
|
+
|
13
|
+
@engine.should_not have_errors
|
14
|
+
@engine.should_not have_remaining_expressions
|
15
|
+
|
16
|
+
purge_engine
|
17
|
+
end
|
18
|
+
|
19
|
+
def wait( fei )
|
20
|
+
Thread.pass
|
21
|
+
return if @terminated_processes.include?( fei.wfid )
|
22
|
+
@engine.wait_for( fei )
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Spec::Matchers.define :have_errors do |*args|
|
2
|
+
match do |engine|
|
3
|
+
@ps = if fei = args.shift
|
4
|
+
engine.process_status( fei )
|
5
|
+
else
|
6
|
+
engine.process_statuses.values.first
|
7
|
+
end
|
8
|
+
|
9
|
+
if @ps
|
10
|
+
@ps.errors.size != 0
|
11
|
+
else
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
failure_message_for_should do |engine|
|
17
|
+
"Expected engine to have errors, but didn't"
|
18
|
+
end
|
19
|
+
failure_message_for_should_not do |engine|
|
20
|
+
"Expected the engine to not have errors, but it did.\n" +
|
21
|
+
@ps.errors.values.map do |e|
|
22
|
+
" * error: #{e.error_class} \"#{e.stacktrace}\""
|
23
|
+
end.join("\n")
|
24
|
+
end
|
25
|
+
description do
|
26
|
+
#
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Matchers.define :have_remaining_expressions do
|
31
|
+
match do |engine|
|
32
|
+
exp_count = engine.get_expression_storage.size
|
33
|
+
|
34
|
+
if exp_count == 1
|
35
|
+
false
|
36
|
+
else
|
37
|
+
50.times { Thread.pass }
|
38
|
+
exp_count = engine.get_expression_storage.size
|
39
|
+
if exp_count == 1
|
40
|
+
false
|
41
|
+
else
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
failure_message_for_should do |engine|
|
48
|
+
"Expected engine to have processes remaining, but it didn't"
|
49
|
+
end
|
50
|
+
failure_message_for_should_not do |engine|
|
51
|
+
"Expected engine to have no processes remaining, but it did.#{engine.get_expression_storage.to_s}"
|
52
|
+
end
|
53
|
+
description do
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/ruote-amqp.rb'}"
|
9
|
+
puts "Loading ruote-amqp gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RuoteAMQP::Listener do
|
4
|
+
|
5
|
+
it "should handle replies" do
|
6
|
+
pdef = <<-EOF
|
7
|
+
class AmqpParticipant2 < OpenWFE::ProcessDefinition
|
8
|
+
|
9
|
+
set :field => 'foo', :value => 'foo'
|
10
|
+
|
11
|
+
sequence do
|
12
|
+
echo '${f:foo}'
|
13
|
+
amqp :queue => 'test3'
|
14
|
+
echo '${f:foo}'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
EOF
|
18
|
+
|
19
|
+
@engine.register_participant( :amqp, RuoteAMQP::Participant )
|
20
|
+
|
21
|
+
@engine.register_listener( RuoteAMQP::Listener )
|
22
|
+
|
23
|
+
fei = @engine.launch pdef
|
24
|
+
|
25
|
+
begin
|
26
|
+
Timeout::timeout(10) do
|
27
|
+
msg = nil
|
28
|
+
MQ.queue('test3').subscribe { |msg| @msg = msg }
|
29
|
+
|
30
|
+
loop do
|
31
|
+
break unless @msg.nil?
|
32
|
+
sleep 0.1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue Timeout::Error
|
36
|
+
violated "Timeout waiting for message"
|
37
|
+
end
|
38
|
+
|
39
|
+
wi = OpenWFE::InFlowWorkItem.from_h( OpenWFE::Json.decode( @msg ) )
|
40
|
+
wi.attributes['foo'] = "bar"
|
41
|
+
|
42
|
+
MQ.queue( wi.attributes['reply_queue'] ).publish( OpenWFE::Json.encode( wi.to_h ) )
|
43
|
+
|
44
|
+
wait( fei )
|
45
|
+
|
46
|
+
@engine.should_not have_errors( fei )
|
47
|
+
@engine.should_not have_remaining_expressions
|
48
|
+
|
49
|
+
@tracer.to_s.should == "foo\nbar"
|
50
|
+
|
51
|
+
purge_engine
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RuoteAMQP::Participant, :type => :ruote do
|
4
|
+
|
5
|
+
it "should support 'reply anyway' on expression parameter" do
|
6
|
+
|
7
|
+
pdef = <<-EOF
|
8
|
+
class AmqpParticipant0 < OpenWFE::ProcessDefinition
|
9
|
+
|
10
|
+
sequence do
|
11
|
+
amqp :queue => 'test1', 'reply_anyway' => true
|
12
|
+
echo 'done.'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
EOF
|
16
|
+
|
17
|
+
@engine.register_participant( :amqp, RuoteAMQP::Participant )
|
18
|
+
|
19
|
+
run_definition( pdef )
|
20
|
+
|
21
|
+
@tracer.to_s.should == 'done.'
|
22
|
+
|
23
|
+
begin
|
24
|
+
Timeout::timeout(10) do
|
25
|
+
@msg = nil
|
26
|
+
MQ.queue('test1').subscribe { |msg| @msg = msg }
|
27
|
+
|
28
|
+
loop do
|
29
|
+
break unless @msg.nil?
|
30
|
+
sleep 0.1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue Timeout::Error
|
34
|
+
violated "Timeout waiting for message"
|
35
|
+
end
|
36
|
+
|
37
|
+
@msg.should match(/^\{.*\}$/) # JSON message by default
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should support 'reply anyway' as participant configuration" do
|
41
|
+
pdef = <<-EOF
|
42
|
+
class AmqpParticipant0 < OpenWFE::ProcessDefinition
|
43
|
+
|
44
|
+
sequence do
|
45
|
+
amqp :queue => 'test4'
|
46
|
+
echo 'done.'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
EOF
|
50
|
+
|
51
|
+
p = RuoteAMQP::Participant.new( :reply_by_default => true )
|
52
|
+
@engine.register_participant( :amqp, p )
|
53
|
+
|
54
|
+
run_definition( pdef )
|
55
|
+
|
56
|
+
@tracer.to_s.should == "done."
|
57
|
+
|
58
|
+
begin
|
59
|
+
Timeout::timeout(10) do
|
60
|
+
@msg = nil
|
61
|
+
MQ.queue('test4').subscribe { |msg| @msg = msg }
|
62
|
+
|
63
|
+
loop do
|
64
|
+
break unless @msg.nil?
|
65
|
+
sleep 0.1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
rescue Timeout::Error
|
69
|
+
violated "Timeout waiting for message"
|
70
|
+
end
|
71
|
+
|
72
|
+
@msg.should match( /^\{.*\}$/) # JSON message by default
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should support custom messages instead of workitems" do
|
76
|
+
pdef = <<-EOF
|
77
|
+
class AmqpParticipant1 < OpenWFE::ProcessDefinition
|
78
|
+
|
79
|
+
sequence do
|
80
|
+
amqp :queue => 'test2', :message => 'foo', 'reply_anyway' => true
|
81
|
+
echo 'done.'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
EOF
|
85
|
+
|
86
|
+
@engine.register_participant( :amqp, RuoteAMQP::Participant )
|
87
|
+
|
88
|
+
run_definition( pdef )
|
89
|
+
|
90
|
+
@tracer.to_s.should == "done."
|
91
|
+
|
92
|
+
begin
|
93
|
+
Timeout::timeout(10) do
|
94
|
+
@msg = nil
|
95
|
+
MQ.queue('test2').subscribe { |msg| @msg = msg }
|
96
|
+
|
97
|
+
loop do
|
98
|
+
break unless @msg.nil?
|
99
|
+
sleep 0.1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
rescue Timeout::Error
|
103
|
+
violated "Timeout waiting for message"
|
104
|
+
end
|
105
|
+
|
106
|
+
@msg.should == 'foo'
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should support a default queue name" do
|
110
|
+
|
111
|
+
pdef = <<-EOF
|
112
|
+
class AmqpParticipant0 < OpenWFE::ProcessDefinition
|
113
|
+
|
114
|
+
sequence do
|
115
|
+
amqp 'reply_anyway' => true
|
116
|
+
echo 'done.'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
EOF
|
120
|
+
|
121
|
+
amqp = RuoteAMQP::Participant.new( :default_queue => 'test5' )
|
122
|
+
@engine.register_participant( :amqp, amqp )
|
123
|
+
|
124
|
+
run_definition( pdef )
|
125
|
+
|
126
|
+
@tracer.to_s.should == 'done.'
|
127
|
+
|
128
|
+
begin
|
129
|
+
Timeout::timeout(10) do
|
130
|
+
@msg = nil
|
131
|
+
MQ.queue('test5').subscribe { |msg| @msg = msg }
|
132
|
+
|
133
|
+
loop do
|
134
|
+
break unless @msg.nil?
|
135
|
+
sleep 0.1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
rescue Timeout::Error
|
139
|
+
violated "Timeout waiting for message"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should support mapping participant names to queue names" do
|
144
|
+
pdef = <<-EOF
|
145
|
+
class AmqpParticipant0 < OpenWFE::ProcessDefinition
|
146
|
+
|
147
|
+
sequence do
|
148
|
+
q1
|
149
|
+
q2
|
150
|
+
amqp
|
151
|
+
echo 'done.'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
EOF
|
155
|
+
|
156
|
+
amqp = RuoteAMQP::Participant.new( :reply_by_default => true, :default_queue => 'test6' )
|
157
|
+
amqp.map_participant( 'q1', 'test7' )
|
158
|
+
amqp.map_participant( 'q2', 'test8' )
|
159
|
+
@engine.register_participant( :amqp, amqp )
|
160
|
+
@engine.register_participant( :q1, amqp )
|
161
|
+
@engine.register_participant( :q2, amqp )
|
162
|
+
|
163
|
+
run_definition( pdef )
|
164
|
+
|
165
|
+
@tracer.to_s.should == 'done.'
|
166
|
+
|
167
|
+
[ 'test6', 'test7', 'test8' ].each do |q|
|
168
|
+
begin
|
169
|
+
Timeout::timeout(10) do
|
170
|
+
@msg = nil
|
171
|
+
MQ.queue( q ).subscribe { |msg| @msg = msg }
|
172
|
+
|
173
|
+
loop do
|
174
|
+
break unless @msg.nil?
|
175
|
+
sleep 0.1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
rescue Timeout::Error
|
179
|
+
violated "Timeout waiting for message on #{q}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
5
|
+
gem 'rspec'
|
6
|
+
require 'spec'
|
7
|
+
end
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
10
|
+
require 'ruote-amqp'
|
11
|
+
require 'spec/ruote'
|
12
|
+
require 'fileutils'
|
13
|
+
|
14
|
+
# AMQP magic worked here
|
15
|
+
AMQP.settings[:vhost] = '/ruote-test'
|
16
|
+
AMQP.settings[:user] = 'ruote'
|
17
|
+
AMQP.settings[:pass] = 'ruote'
|
18
|
+
|
19
|
+
Spec::Runner.configure do |config|
|
20
|
+
|
21
|
+
config.include( RuoteSpecHelpers )
|
22
|
+
|
23
|
+
config.before(:each) do
|
24
|
+
@tracer = Tracer.new
|
25
|
+
|
26
|
+
ac = {}
|
27
|
+
|
28
|
+
class << ac
|
29
|
+
alias :old_put :[]=
|
30
|
+
def []= (k, v)
|
31
|
+
raise("!!!!! #{k.class}\n#{k.inspect}") \
|
32
|
+
if k.class != String and k.class != Symbol
|
33
|
+
old_put(k, v)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
#
|
37
|
+
# useful for tracking misuses of the application context
|
38
|
+
|
39
|
+
ac['__tracer'] = @tracer
|
40
|
+
ac[:ruby_eval_allowed] = true
|
41
|
+
ac[:definition_in_launchitem_allowed] = true
|
42
|
+
|
43
|
+
@engine = OpenWFE::Engine.new( ac )
|
44
|
+
|
45
|
+
@terminated_processes = []
|
46
|
+
@engine.get_expression_pool.add_observer(:terminate) do |c, fe, wi|
|
47
|
+
@terminated_processes << fe.fei.wfid
|
48
|
+
#p [ :terminated, @terminated_processes ]
|
49
|
+
end
|
50
|
+
|
51
|
+
if ENV['DEBUG']
|
52
|
+
$OWFE_LOG = Logger.new( STDOUT )
|
53
|
+
$OWFE_LOG.level = Logger::DEBUG
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
config.after(:each) do
|
58
|
+
@engine.stop
|
59
|
+
AMQP.stop { EM.stop }
|
60
|
+
sleep 0.001 while EM.reactor_running?
|
61
|
+
end
|
62
|
+
|
63
|
+
config.after(:all) do
|
64
|
+
base = File.expand_path( File.dirname(__FILE__) + '/..' )
|
65
|
+
FileUtils.rm_rf( base + '/logs' )
|
66
|
+
FileUtils.rm_rf( base + '/work' )
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
class Tracer
|
72
|
+
def initialize
|
73
|
+
@trace = ''
|
74
|
+
end
|
75
|
+
def to_s
|
76
|
+
@trace.to_s.strip
|
77
|
+
end
|
78
|
+
def << s
|
79
|
+
@trace << s
|
80
|
+
end
|
81
|
+
def clear
|
82
|
+
@trace = ''
|
83
|
+
end
|
84
|
+
def puts s
|
85
|
+
@trace << "#{s}\n"
|
86
|
+
end
|
87
|
+
end
|
data/tasks/rspec.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
5
|
+
require 'spec'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
puts <<-EOS
|
11
|
+
To use rspec for testing you must install rspec gem:
|
12
|
+
gem install rspec
|
13
|
+
EOS
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run the specs under spec/models"
|
18
|
+
Spec::Rake::SpecTask.new do |t|
|
19
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simianarmy-ruote-amqp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.21
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kenneth Kalmer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-05-31 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ruote
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.21
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: amqp
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.7
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.4.0
|
44
|
+
version:
|
45
|
+
description: |-
|
46
|
+
ruote-amqp provides an AMQP participant/listener pair that allows you to
|
47
|
+
distribute workitems out to AMQP consumers for processing.
|
48
|
+
|
49
|
+
To learn more about remote participants in ruote please see
|
50
|
+
http://openwfe.rubyforge.org/part.html
|
51
|
+
email:
|
52
|
+
- kenneth.kalmer@gmail.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files:
|
58
|
+
- History.txt
|
59
|
+
- Manifest.txt
|
60
|
+
- PostInstall.txt
|
61
|
+
- TODO.txt
|
62
|
+
files:
|
63
|
+
- History.txt
|
64
|
+
- Manifest.txt
|
65
|
+
- PostInstall.txt
|
66
|
+
- README.rdoc
|
67
|
+
- Rakefile
|
68
|
+
- TODO.txt
|
69
|
+
- lib/ruote-amqp.rb
|
70
|
+
- lib/ruote-amqp/listener.rb
|
71
|
+
- lib/ruote-amqp/participant.rb
|
72
|
+
- lib/spec/ruote.rb
|
73
|
+
- lib/spec/ruote_example_group.rb
|
74
|
+
- lib/spec/ruote_helpers.rb
|
75
|
+
- lib/spec/ruote_matchers.rb
|
76
|
+
- script/console
|
77
|
+
- script/destroy
|
78
|
+
- script/generate
|
79
|
+
- spec/listener_spec.rb
|
80
|
+
- spec/participant_spec.rb
|
81
|
+
- spec/spec.opts
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
- tasks/rspec.rake
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/kennethkalmer/ruote-amqp
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message: PostInstall.txt
|
89
|
+
rdoc_options:
|
90
|
+
- --main
|
91
|
+
- README.rdoc
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
version:
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
version:
|
106
|
+
requirements: []
|
107
|
+
|
108
|
+
rubyforge_project: simianarmy-ruote-amqp
|
109
|
+
rubygems_version: 1.3.5
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: ruote-amqp provides an AMQP participant/listener pair that allows you to distribute workitems out to AMQP consumers for processing
|
113
|
+
test_files: []
|
114
|
+
|