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 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
@@ -0,0 +1,4 @@
1
+ ruote-amqp TODO
2
+ ===============
3
+
4
+ [ ] Launch processes over AMQP
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,5 @@
1
+ require File.dirname(__FILE__) + '/ruote_example_group.rb'
2
+ require File.dirname(__FILE__) + '/ruote_matchers.rb'
3
+ require File.dirname(__FILE__) + '/ruote_helpers.rb'
4
+
5
+ Spec::Example::ExampleGroupFactory.register(:ruote, Spec::Ruote::Example::RuoteExampleGroup)
@@ -0,0 +1,8 @@
1
+ module Spec
2
+ module Ruote
3
+ module Example
4
+ class RuoteExampleGroup < Spec::Example::ExampleGroup
5
+ end
6
+ end
7
+ end
8
+ end
@@ -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
@@ -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
+