ruote-amqp 0.9.21.1 → 2.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/History.txt +7 -0
- data/Manifest.txt +4 -2
- data/README.rdoc +28 -22
- data/Rakefile +45 -21
- data/lib/ruote-amqp.rb +46 -20
- data/lib/ruote-amqp/launchitem_listener.rb +88 -0
- data/lib/ruote-amqp/participant.rb +14 -24
- data/lib/ruote-amqp/workitem_listener.rb +84 -0
- data/lib/spec/ruote.rb +1 -1
- data/lib/spec/ruote_example_group.rb +1 -1
- data/lib/spec/ruote_helpers.rb +12 -9
- data/lib/spec/ruote_matchers.rb +15 -25
- data/ruote-amqp.gemspec +83 -0
- data/spec/launchitem_listener_spec.rb +32 -0
- data/spec/participant_spec.rb +9 -21
- data/spec/spec_helper.rb +33 -41
- data/spec/{listener_spec.rb → workitem_listener_spec.rb} +12 -13
- metadata +40 -29
- data/lib/ruote-amqp/listener.rb +0 -92
- data/tasks/rspec.rake +0 -21
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 2.0.0 (WIP)
|
2
|
+
|
3
|
+
* Compatible with ruote 2.0
|
4
|
+
* Thanks to John Mettraux (http://github.com/jmettraux/ruote-amqp)
|
5
|
+
* Thanks to Jason & Jordan (http://github.com/asm/ruote-amqp)
|
6
|
+
* Thanks to Charles Magid (http://github.com/ChasManRors/ruote-amqp)
|
7
|
+
|
1
8
|
=== 0.9.21.1 2009-08-03
|
2
9
|
|
3
10
|
* Switch to using persistent AMQP messages by default
|
data/Manifest.txt
CHANGED
@@ -5,7 +5,8 @@ README.rdoc
|
|
5
5
|
Rakefile
|
6
6
|
TODO.txt
|
7
7
|
lib/ruote-amqp.rb
|
8
|
-
lib/ruote-amqp/
|
8
|
+
lib/ruote-amqp/launchitem_listener.rb
|
9
|
+
lib/ruote-amqp/workitem_listener.rb
|
9
10
|
lib/ruote-amqp/participant.rb
|
10
11
|
lib/spec/ruote.rb
|
11
12
|
lib/spec/ruote_example_group.rb
|
@@ -14,7 +15,8 @@ lib/spec/ruote_matchers.rb
|
|
14
15
|
script/console
|
15
16
|
script/destroy
|
16
17
|
script/generate
|
17
|
-
spec/
|
18
|
+
spec/launchitem_listener_spec.rb
|
19
|
+
spec/workitem_listener_spec.rb
|
18
20
|
spec/participant_spec.rb
|
19
21
|
spec/ruote_amqp_spec.rb
|
20
22
|
spec/spec.opts
|
data/README.rdoc
CHANGED
@@ -1,54 +1,58 @@
|
|
1
1
|
= ruote-amqp
|
2
2
|
|
3
3
|
* http://github.com/kennethkalmer/ruote-amqp
|
4
|
-
* http://
|
4
|
+
* http://rdoc.info/projects/kennethkalmer/ruote-amqp
|
5
|
+
* http://ruote.rubyforge.org
|
5
6
|
|
6
7
|
== DESCRIPTION:
|
7
8
|
|
8
9
|
ruote-amqp provides an AMQP participant/listener pair that allows you to
|
9
|
-
distribute workitems out to AMQP consumers for processing
|
10
|
+
distribute workitems out to AMQP consumers for processing, as well as launching
|
11
|
+
processes over AMQP.
|
10
12
|
|
11
13
|
To learn more about remote participants in ruote please see
|
12
|
-
http://
|
14
|
+
http://ruote.rubyforge.org/part_implementations.html
|
13
15
|
|
14
16
|
== FEATURES/PROBLEMS:
|
15
17
|
|
16
18
|
* Flexible participant for sending workitems
|
17
19
|
* Flexible listener for receiving replies
|
20
|
+
* Flexible launch item listener for launching processes over AMQP
|
18
21
|
* Fully evented (thanks to the amqp gem)
|
19
22
|
|
20
23
|
== SYNOPSIS:
|
21
24
|
|
22
|
-
Please review the
|
25
|
+
Please review the rdoc in RuoteAMQP::Participant and Ruote::AMQP::Listener
|
23
26
|
|
24
27
|
== REQUIREMENTS:
|
25
28
|
|
26
|
-
* ruote[http://
|
27
|
-
* amqp[http://github.com/tmm1/amqp] 0.6.
|
29
|
+
* ruote[http://ruote.rubyforge.org] 2.1.4 or later
|
30
|
+
* amqp[http://github.com/tmm1/amqp] 0.6.6 or later
|
31
|
+
* rufus-json[http://github.com/jmettraux/rufus-json] 0.1.0 or later
|
32
|
+
* rabbitmq[http://www.rabbitmq.com/] 1.6.0 or later
|
28
33
|
|
29
|
-
|
34
|
+
== INSTALL:
|
30
35
|
|
31
|
-
|
32
|
-
Mettraux is working tirelessly to ship ruote 2.0. To build your own ruote
|
33
|
-
0.9.21 gem run these commands:
|
36
|
+
Please be sure to have read the requirements section above
|
34
37
|
|
35
|
-
|
36
|
-
$ cd ruote
|
37
|
-
$ rake gem
|
38
|
-
$ sudo gem install pkg/ruote-0.9.21.gem
|
38
|
+
* sudo gem install ruote-amqp
|
39
39
|
|
40
|
-
|
40
|
+
== TESTS:
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
To run the tests you need the following requirements met, or the testing environment
|
43
|
+
will fail horribly.
|
44
44
|
|
45
|
-
|
45
|
+
=== RabbitMQ vhost
|
46
46
|
|
47
|
-
|
47
|
+
The tests use dedicated vhost on a running AMQP broker. To configure RabbitMQ
|
48
|
+
you can run the following commands:
|
48
49
|
|
49
|
-
|
50
|
+
# rabbitmqctl add_vhost ruote-test
|
51
|
+
# rabbitmqctl add_user ruote ruote
|
52
|
+
# rabbitmqctl set_permissions -p ruote-test ruote '.*' '.*' '.*'
|
50
53
|
|
51
|
-
|
54
|
+
If you need to change the AMQP configuration used by the tests, edit the
|
55
|
+
+spec/spec_helper.rb+ file.
|
52
56
|
|
53
57
|
== DAEMON-KIT:
|
54
58
|
|
@@ -59,11 +63,13 @@ to write your remote participants.
|
|
59
63
|
daemon-kit offers plenty of convenience for remote participants and includes
|
60
64
|
a code generator for ruote remote participants.
|
61
65
|
|
66
|
+
DaemonKit doesn't currently support ruote 2.1, support is forthcoming.
|
67
|
+
|
62
68
|
== LICENSE:
|
63
69
|
|
64
70
|
(The MIT License)
|
65
71
|
|
66
|
-
Copyright (c)
|
72
|
+
Copyright (c) 2010 Kenneth Kalmer
|
67
73
|
|
68
74
|
Permission is hereby granted, free of charge, to any person obtaining
|
69
75
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -1,26 +1,50 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
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 '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.20'], ['amqp', '= 0.6.0']]
|
2
|
+
require 'rake'
|
18
3
|
|
4
|
+
require 'lib/ruote-amqp'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gemspec|
|
9
|
+
gemspec.name = 'ruote-amqp'
|
10
|
+
gemspec.version = RuoteAMQP::VERSION
|
11
|
+
gemspec.summary = 'AMQP participant/listener pair for ruote 2.1'
|
12
|
+
gemspec.email = 'kenneth.kalmer@gmail.com'
|
13
|
+
gemspec.homepage = 'http://github.com/kennethkalmer/ruote-amqp'
|
14
|
+
gemspec.authors = ['kenneth.kalmer@gmail.com']
|
15
|
+
gemspec.extra_rdoc_files.include '*.txt'
|
16
|
+
|
17
|
+
gemspec.add_dependency 'rufus-json', '>= 0.1.0'
|
18
|
+
gemspec.add_dependency 'amqp', '>= 0.6.6'
|
19
|
+
gemspec.add_dependency 'ruote', '>= 2.1.5'
|
20
|
+
gemspec.add_development_dependency 'rspec'
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler not available. Install it with 'gem install jeweler'"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
19
31
|
end
|
20
32
|
|
21
|
-
|
22
|
-
|
33
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
34
|
+
spec.libs << 'lib' << 'spec'
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :spec #=> :check_dependencies
|
23
40
|
|
24
|
-
|
25
|
-
|
26
|
-
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
begin
|
44
|
+
require 'yard'
|
45
|
+
YARD::Rake::YardocTask.new
|
46
|
+
rescue LoadError
|
47
|
+
task :yardoc do
|
48
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
49
|
+
end
|
50
|
+
end
|
data/lib/ruote-amqp.rb
CHANGED
@@ -1,20 +1,3 @@
|
|
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
1
|
require 'mq'
|
19
2
|
|
20
3
|
# AMQP participant and listener pair for ruote.
|
@@ -31,10 +14,12 @@ require 'mq'
|
|
31
14
|
# to be restarted in order for messages to be resent.
|
32
15
|
#
|
33
16
|
module RuoteAMQP
|
34
|
-
VERSION = '0.9.21.1'
|
35
17
|
|
36
|
-
|
37
|
-
|
18
|
+
VERSION = '2.1.5'
|
19
|
+
|
20
|
+
autoload 'Participant', 'ruote-amqp/participant'
|
21
|
+
autoload 'WorkitemListener', 'ruote-amqp/workitem_listener'
|
22
|
+
autoload 'LaunchitemListener', 'ruote-amqp/launchitem_listener'
|
38
23
|
|
39
24
|
class << self
|
40
25
|
|
@@ -45,5 +30,46 @@ module RuoteAMQP
|
|
45
30
|
@use_persistent_messages = true if @use_persistent_messages.nil?
|
46
31
|
@use_persistent_messages
|
47
32
|
end
|
33
|
+
|
34
|
+
# Ensure the AMQP connection is started
|
35
|
+
def start!
|
36
|
+
return if started?
|
37
|
+
|
38
|
+
mutex = Mutex.new
|
39
|
+
cv = ConditionVariable.new
|
40
|
+
|
41
|
+
Thread.main[:ruote_amqp_connection] = Thread.new do
|
42
|
+
Thread.abort_on_exception = true
|
43
|
+
AMQP.start {
|
44
|
+
started!
|
45
|
+
cv.signal
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
mutex.synchronize { cv.wait(mutex) }
|
50
|
+
|
51
|
+
MQ.prefetch(1)
|
52
|
+
|
53
|
+
yield if block_given?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check whether the AMQP connection is started
|
57
|
+
def started?
|
58
|
+
Thread.main[:ruote_amqp_started] == true
|
59
|
+
end
|
60
|
+
|
61
|
+
def started! #:nodoc:
|
62
|
+
Thread.main[:ruote_amqp_started] = true
|
63
|
+
end
|
64
|
+
|
65
|
+
# Close down the AMQP connections
|
66
|
+
def stop!
|
67
|
+
return unless started?
|
68
|
+
|
69
|
+
AMQP.stop
|
70
|
+
Thread.main[:ruote_amqp_connection].join
|
71
|
+
Thread.main[:ruote_amqp_started] = false
|
72
|
+
end
|
73
|
+
|
48
74
|
end
|
49
75
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module RuoteAMQP
|
2
|
+
|
3
|
+
# = AMQP Launchitem Listener
|
4
|
+
#
|
5
|
+
# Used on its own, the RuoteAMQP::LaunchitemListener provides the engine with
|
6
|
+
# a way to launch process definitions over an AMQP direct exchange.
|
7
|
+
#
|
8
|
+
# == Message Format
|
9
|
+
#
|
10
|
+
# The LaunchitemListener expects JSON formatted messages that look like this:
|
11
|
+
#
|
12
|
+
# {
|
13
|
+
# "definition" : "process definition",
|
14
|
+
# "fields" : { "key" : "value" },
|
15
|
+
# "variables" : { "key" : "value" }
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# The definition key is a complete string representation of a business process.
|
19
|
+
#
|
20
|
+
# == Configuration
|
21
|
+
#
|
22
|
+
# AMQP configuration is handled by directly manipulating the values of the
|
23
|
+
# +AMQP.settings+ hash, as provided by the AMQP gem. No defaults are set by
|
24
|
+
# the listener. The only +option+ parsed by the initializer is the +queue+
|
25
|
+
# key (in the optional hash). If no +queue+ key is provided, the listener
|
26
|
+
# will subscribe to the +ruote_launchitems+ direct exchange for launchitems.
|
27
|
+
#
|
28
|
+
# The listener requires version 0.6.6 or later of the amqp gem.
|
29
|
+
#
|
30
|
+
# == Usage
|
31
|
+
#
|
32
|
+
# Register the engine with the listener:
|
33
|
+
#
|
34
|
+
# RuoteAMQP::LaunchitemListener.new( engine_instance )
|
35
|
+
#
|
36
|
+
# The workitem listener leverages the asynchronous nature of the amqp gem,
|
37
|
+
# so no timers are setup when initialized.
|
38
|
+
class LaunchitemListener < Ruote::Receiver
|
39
|
+
|
40
|
+
class << self
|
41
|
+
|
42
|
+
# Listening queue - set this before initialization
|
43
|
+
attr_writer :queue
|
44
|
+
|
45
|
+
def queue
|
46
|
+
@queue ||= 'ruote_launchitems'
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Start a new LaunchItem listener
|
52
|
+
#
|
53
|
+
# @param [ Ruote::Engine ] An instance of a ruote engine
|
54
|
+
# @param [ String ] Optional queue name
|
55
|
+
def initialize( engine, queue = nil )
|
56
|
+
|
57
|
+
self.class.queue = queue if queue
|
58
|
+
|
59
|
+
RuoteAMQP.start!
|
60
|
+
|
61
|
+
MQ.queue( self.class.queue, :durable => true ).subscribe do |message|
|
62
|
+
if AMQP.closing?
|
63
|
+
# Do nothing, we're going down
|
64
|
+
else
|
65
|
+
launchitem = decode_launchitem( message )
|
66
|
+
engine.launch( *launchitem )
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def stop
|
72
|
+
RuoteAMQP.stop!
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Complicated guesswork that needs to happen here to detect the format
|
78
|
+
def decode_launchitem( msg )
|
79
|
+
hash = Rufus::Json.decode( msg )
|
80
|
+
opts = {}
|
81
|
+
definition = hash.delete('definition')
|
82
|
+
fields = hash.delete('fields') || {}
|
83
|
+
variables = hash.delete('variables') || {}
|
84
|
+
|
85
|
+
[ definition, fields, variables ]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ruote/part/local_participant'
|
2
|
+
|
1
3
|
module RuoteAMQP
|
2
4
|
|
3
5
|
# = AMQP Participants
|
@@ -111,14 +113,15 @@ module RuoteAMQP
|
|
111
113
|
# #RuoteAMQP)
|
112
114
|
#
|
113
115
|
class Participant
|
114
|
-
|
116
|
+
|
117
|
+
include Ruote::LocalParticipant
|
115
118
|
|
116
119
|
# Accepts an options hash with the following keys:
|
117
120
|
#
|
118
121
|
# * :reply_by_default => (bool) false by default
|
119
122
|
# * :default_queue => (string) nil by default
|
120
123
|
def initialize( options = {} )
|
121
|
-
|
124
|
+
RuoteAMQP.start!
|
122
125
|
|
123
126
|
@options = {
|
124
127
|
:reply_by_default => false,
|
@@ -140,57 +143,44 @@ module RuoteAMQP
|
|
140
143
|
# To force the participant to reply to the engine, set the
|
141
144
|
# +reply_anyway+ workitem parameter.
|
142
145
|
def consume( workitem )
|
143
|
-
ldebug { "consuming workitem" }
|
144
|
-
ensure_reactor!
|
145
|
-
|
146
146
|
if target_queue = determine_queue( workitem )
|
147
147
|
|
148
148
|
q = MQ.queue( target_queue, :durable => true )
|
149
149
|
|
150
150
|
# Message or workitem?
|
151
|
-
if message = ( workitem.
|
152
|
-
ldebug { "sending message to queue: #{target_queue}" }
|
151
|
+
if message = ( workitem.fields['message'] || workitem.fields['params']['message'] )
|
153
152
|
q.publish( message, :persistent => RuoteAMQP.use_persistent_messages? )
|
154
|
-
|
155
153
|
else
|
156
|
-
ldebug { "sending workitem to queue: #{target_queue}" }
|
157
|
-
|
158
154
|
q.publish( encode_workitem( workitem ), :persistent => RuoteAMQP.use_persistent_messages? )
|
159
155
|
end
|
160
156
|
else
|
161
|
-
|
157
|
+
raise "no queue in workitem params!"
|
162
158
|
end
|
163
159
|
|
164
|
-
if @options[:reply_by_default] || workitem.params['
|
160
|
+
if @options[:reply_by_default] || workitem.fields['params']['reply_anyway'] == true
|
165
161
|
reply_to_engine( workitem )
|
166
162
|
end
|
167
|
-
|
168
|
-
ldebug { "done" }
|
169
163
|
end
|
170
164
|
|
171
165
|
def stop
|
172
|
-
|
166
|
+
RuoteAMQP.stop!
|
167
|
+
end
|
173
168
|
|
174
|
-
|
175
|
-
@em_thread.join if @em_thread
|
169
|
+
def cancel(fei, flavour)
|
176
170
|
end
|
177
171
|
|
178
172
|
private
|
179
173
|
|
180
174
|
def determine_queue( workitem )
|
181
|
-
workitem.params['queue'] ||
|
175
|
+
workitem.fields['params']['queue'] ||
|
182
176
|
@participant_maps[ workitem.participant_name ] ||
|
183
177
|
@options[:default_queue]
|
184
178
|
end
|
185
179
|
|
186
180
|
# Encode (and extend) the workitem as JSON
|
187
181
|
def encode_workitem( wi )
|
188
|
-
wi.
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
|
-
def ensure_reactor!
|
193
|
-
@em_thread = Thread.new { EM.run } unless EM.reactor_running?
|
182
|
+
wi.fields['params']['reply_queue'] = WorkitemListener.queue
|
183
|
+
wi.to_h.to_json
|
194
184
|
end
|
195
185
|
end
|
196
186
|
end
|