ruote-amqp 2.1.5 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,147 @@
1
+
2
+ require 'ruote-amqp'
3
+
4
+
5
+ module RuoteAMQP
6
+
7
+ #
8
+ # = AMQP Receiver
9
+ #
10
+ # Used in conjunction with the RuoteAMQP::Participant, the WorkitemListener
11
+ # subscribes to a specific direct exchange and monitors for
12
+ # incoming workitems. It expects workitems to arrive serialized as
13
+ # JSON.
14
+ #
15
+ # == Configuration
16
+ #
17
+ # AMQP configuration is handled by directly manipulating the values of
18
+ # the +AMQP.settings+ hash, as provided by the AMQP gem. No
19
+ # defaults are set by the listener. The only +option+ parsed by
20
+ # the initializer of the workitem listener is the +queue+ key (Hash
21
+ # expected). If no +queue+ key is set, the listener will subscribe
22
+ # to the +ruote_workitems+ direct exchange for workitems, otherwise it will
23
+ # subscribe to the direct exchange provided.
24
+ #
25
+ # == Usage
26
+ #
27
+ # Register the engine or storage with the listener:
28
+ #
29
+ # RuoteAMQP::Receiver.new(engine_or_storage)
30
+ #
31
+ # The workitem listener leverages the asynchronous nature of the amqp gem,
32
+ # so no timers are setup when initialized.
33
+ #
34
+ # == Options
35
+ #
36
+ # :queue and :launchitems
37
+ #
38
+ # See the RuoteAMQP::Participant docs for information on sending
39
+ # workitems out to remote participants, and have them send replies
40
+ # to the correct direct exchange specified in the workitem
41
+ # attributes.
42
+ #
43
+ class Receiver < Ruote::Receiver
44
+
45
+ attr_reader :queue
46
+
47
+ # Starts a new Receiver
48
+ #
49
+ # Two arguments for this method.
50
+ #
51
+ # The first oone should be a Ruote::Engine, a Ruote::Storage or
52
+ # a Ruote::Worker instance.
53
+ #
54
+ # The second one is a hash for options. There are two known options :
55
+ #
56
+ # :queue for setting the queue on which to listen (defaults to
57
+ # 'ruote_workitems').
58
+ #
59
+ # The :launchitems option :
60
+ #
61
+ # :launchitems => true
62
+ # # the receiver accepts workitems and launchitems
63
+ # :launchitems => false
64
+ # # the receiver only accepts workitems
65
+ # :launchitems => :only
66
+ # # the receiver only accepts launchitems
67
+ #
68
+ def initialize(engine_or_storage, opts={})
69
+
70
+ super(engine_or_storage)
71
+
72
+ @launchitems = opts[:launchitems]
73
+
74
+ @queue =
75
+ opts[:queue] ||
76
+ (@launchitems == :only ? 'ruote_launchitems' : 'ruote_workitems')
77
+
78
+ RuoteAMQP.start!
79
+
80
+ if opts[:unsubscribe]
81
+ MQ.queue(@queue, :durable => true).unsubscribe
82
+ sleep 0.300
83
+ end
84
+
85
+ MQ.queue(@queue, :durable => true).subscribe do |message|
86
+ if AMQP.closing?
87
+ # do nothing, we're going down
88
+ else
89
+ handle(message)
90
+ end
91
+ end
92
+ end
93
+
94
+ def stop
95
+
96
+ RuoteAMQP.stop!
97
+ end
98
+
99
+ # (feel free to overwrite me)
100
+ #
101
+ def decode_workitem(msg)
102
+
103
+ (Rufus::Json.decode(msg) rescue nil)
104
+ end
105
+
106
+ private
107
+
108
+ def handle(msg)
109
+
110
+ item = decode_workitem(msg)
111
+
112
+ return unless item.is_a?(Hash)
113
+
114
+ not_li = ! item.has_key?('definition')
115
+
116
+ return if @launchitems == :only && not_li
117
+ return unless @launchitems || not_li
118
+
119
+ if not_li
120
+ receive(item) # workitem resumes in its process instance
121
+ else
122
+ launch(item) # new process instance launch
123
+ end
124
+
125
+ rescue => e
126
+ # something went wrong
127
+ # let's simply discard the message
128
+ $stderr.puts('=' * 80)
129
+ $stderr.puts(self.class.name)
130
+ $stderr.puts("couldn't handle incoming message :")
131
+ $stderr.puts('')
132
+ $stderr.puts(msg.inspect)
133
+ $stderr.puts('')
134
+ $stderr.puts(Rufus::Json.pretty_encode(item)) rescue nil
135
+ $stderr.puts('')
136
+ $stderr.puts(e.inspect)
137
+ $stderr.puts(e.backtrace)
138
+ $stderr.puts('=' * 80)
139
+ end
140
+
141
+ def launch(hash)
142
+
143
+ super(hash['definition'], hash['fields'] || {}, hash['variables'] || {})
144
+ end
145
+ end
146
+ end
147
+
@@ -0,0 +1,6 @@
1
+
2
+ module RuoteAMQP
3
+
4
+ VERSION = '2.2.0'
5
+ end
6
+
@@ -1,84 +1,12 @@
1
+
1
2
  module RuoteAMQP
2
3
 
3
4
  #
4
- # = AMQP Workitem Listener
5
- #
6
- # Used in conjunction with the RuoteAMQP::Participant, the WorkitemListener
7
- # subscribes to a specific direct exchange and monitors for
8
- # incoming workitems. It expects workitems to arrive serialized as
9
- # JSON.
10
- #
11
- # == Configuration
12
- #
13
- # AMQP configuration is handled by directly manipulating the values of
14
- # the +AMQP.settings+ hash, as provided by the AMQP gem. No
15
- # defaults are set by the listener. The only +option+ parsed by
16
- # the initializer of the workitem listener is the +queue+ key (Hash
17
- # expected). If no +queue+ key is set, the listener will subscribe
18
- # to the +ruote_workitems+ direct exchange for workitems, otherwise it will
19
- # subscribe to the direct exchange provided.
20
- #
21
- # The participant requires version 0.6.6 or later of the amqp gem.
22
- #
23
- # == Usage
5
+ # Got replaced by RuoteAMQP::Receiver
24
6
  #
25
- # Register the engine or storage with the listener:
7
+ # This class is kept for backward compatibility.
26
8
  #
27
- # RuoteAMQP::WorkitemListener.new( engine_or_storage )
28
- #
29
- # The workitem listener leverages the asynchronous nature of the amqp gem,
30
- # so no timers are setup when initialized.
31
- #
32
- # See the RuoteAMQP::Participant docs for information on sending
33
- # workitems out to remote participants, and have them send replies
34
- # to the correct direct exchange specified in the workitem
35
- # attributes.
36
- #
37
- class WorkitemListener < Ruote::Receiver
38
-
39
- class << self
40
-
41
- # Listening queue - set this before initialization
42
- attr_writer :queue
43
-
44
- def queue
45
- @queue ||= 'ruote_workitems'
46
- end
47
-
48
- end
49
-
50
- # Starts a new WorkitemListener
51
- #
52
- # @param [ Ruote::Engine, Ruote::Storage ] A configured ruote engine or storage instance
53
- # @param [ String ] An optional queue name
54
- def initialize( engine_or_storage, queue = nil )
55
-
56
- @storage = engine_or_storage.respond_to?( :storage ) ? engine_or_storage.storage : engine_or_storage
57
-
58
- self.class.queue = queue if queue
59
-
60
- RuoteAMQP.start!
61
-
62
- MQ.queue( self.class.queue, :durable => true ).subscribe do |message|
63
- if AMQP.closing?
64
- # Do nothing, we're going down
65
- else
66
- workitem = decode_workitem( message )
67
- reply( workitem )
68
- end
69
- end
70
- end
71
-
72
- def stop
73
- RuoteAMQP.stop!
74
- end
75
-
76
- private
77
-
78
- # Complicated guesswork that needs to happen here to detect the format
79
- def decode_workitem( msg )
80
- hash = Rufus::Json.decode( msg )
81
- Ruote::Workitem.new( hash )
82
- end
9
+ class WorkitemListener < ::RuoteAMQP::Receiver
83
10
  end
84
11
  end
12
+
data/ruote-amqp.gemspec CHANGED
@@ -1,83 +1,32 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
1
+ # encoding: utf-8
5
2
 
6
3
  Gem::Specification.new do |s|
7
- s.name = %q{ruote-amqp}
8
- s.version = "2.1.5"
9
4
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["kenneth.kalmer@gmail.com"]
12
- s.date = %q{2010-02-04}
13
- s.email = %q{kenneth.kalmer@gmail.com}
14
- s.extra_rdoc_files = [
15
- "History.txt",
16
- "Manifest.txt",
17
- "PostInstall.txt",
18
- "README.rdoc",
19
- "TODO.txt"
20
- ]
21
- s.files = [
22
- ".gitignore",
23
- "History.txt",
24
- "Manifest.txt",
25
- "PostInstall.txt",
26
- "README.rdoc",
27
- "Rakefile",
28
- "TODO.txt",
29
- "lib/ruote-amqp.rb",
30
- "lib/ruote-amqp/launchitem_listener.rb",
31
- "lib/ruote-amqp/participant.rb",
32
- "lib/ruote-amqp/workitem_listener.rb",
33
- "lib/spec/ruote.rb",
34
- "lib/spec/ruote_example_group.rb",
35
- "lib/spec/ruote_helpers.rb",
36
- "lib/spec/ruote_matchers.rb",
37
- "ruote-amqp.gemspec",
38
- "script/console",
39
- "script/destroy",
40
- "script/generate",
41
- "spec/launchitem_listener_spec.rb",
42
- "spec/participant_spec.rb",
43
- "spec/ruote_amqp_spec.rb",
44
- "spec/spec.opts",
45
- "spec/spec_helper.rb",
46
- "spec/workitem_listener_spec.rb"
47
- ]
48
- s.homepage = %q{http://github.com/kennethkalmer/ruote-amqp}
49
- s.rdoc_options = ["--charset=UTF-8"]
50
- s.require_paths = ["lib"]
51
- s.rubygems_version = %q{1.3.5}
52
- s.summary = %q{AMQP participant/listener pair for ruote 2.1}
53
- s.test_files = [
54
- "spec/launchitem_listener_spec.rb",
55
- "spec/participant_spec.rb",
56
- "spec/ruote_amqp_spec.rb",
57
- "spec/spec_helper.rb",
58
- "spec/workitem_listener_spec.rb"
5
+ s.name = 'ruote-amqp'
6
+ s.version = File.read('lib/ruote-amqp/version.rb').match(/VERSION = '([^']+)'/)[1]
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = [ 'Kenneth Kalmer', 'John Mettraux' ]
9
+ s.email = [ 'kenneth.kalmer@gmail.com', 'jmettraux@gmail.com' ]
10
+ s.homepage = 'http://ruote.rubyforge.org'
11
+ s.rubyforge_project = 'ruote'
12
+ s.summary = 'AMQP participant/listener pair for ruote 2.1'
13
+ s.description = %{
14
+ AMQP participant/listener pair for ruote 2.1
15
+ }
16
+
17
+ #s.files = `git ls-files`.split("\n")
18
+ s.files = Dir[
19
+ 'Rakefile',
20
+ 'lib/**/*.rb', 'spec/**/*.rb', 'test/**/*.rb',
21
+ '*.gemspec', '*.txt', '*.rdoc', '*.md'
59
22
  ]
60
23
 
61
- if s.respond_to? :specification_version then
62
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
- s.specification_version = 3
24
+ s.add_runtime_dependency 'amqp', '0.7.0'
25
+ s.add_runtime_dependency 'ruote', ">= #{s.version}"
26
+
27
+ s.add_development_dependency 'rake'
28
+ s.add_development_dependency 'rspec', ">= 2.2.1"
64
29
 
65
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
66
- s.add_runtime_dependency(%q<rufus-json>, [">= 0.1.0"])
67
- s.add_runtime_dependency(%q<amqp>, [">= 0.6.6"])
68
- s.add_runtime_dependency(%q<ruote>, [">= 2.1.5"])
69
- s.add_development_dependency(%q<rspec>, [">= 0"])
70
- else
71
- s.add_dependency(%q<rufus-json>, [">= 0.1.0"])
72
- s.add_dependency(%q<amqp>, [">= 0.6.6"])
73
- s.add_dependency(%q<ruote>, [">= 2.1.5"])
74
- s.add_dependency(%q<rspec>, [">= 0"])
75
- end
76
- else
77
- s.add_dependency(%q<rufus-json>, [">= 0.1.0"])
78
- s.add_dependency(%q<amqp>, [">= 0.6.6"])
79
- s.add_dependency(%q<ruote>, [">= 2.1.5"])
80
- s.add_dependency(%q<rspec>, [">= 0"])
81
- end
30
+ s.require_path = 'lib'
82
31
  end
83
32
 
@@ -1,32 +1,70 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ #
5
+ # NOTE : RuoteAMQP::LaunchitemListener has been depreacted in favour of
6
+ # RuoteAMQP::Receiver
7
+ #
2
8
 
3
9
  describe RuoteAMQP::LaunchitemListener do
4
10
 
5
- it "should launch processes" do
11
+ after(:each) do
12
+ purge_engine
13
+ end
14
+
15
+ it 'launches processes' do
16
+
6
17
  json = {
7
- "definition" => "
18
+ 'definition' => %{
8
19
  Ruote.process_definition :name => 'test' do
9
20
  sequence do
10
21
  echo '${f:foo}'
11
22
  end
12
23
  end
13
- ",
14
- "fields" => {
15
- "foo" => "bar"
16
- }
24
+ },
25
+ 'fields' => { 'foo' => 'bar' }
17
26
  }.to_json
18
27
 
19
- RuoteAMQP::LaunchitemListener.new( @engine )
28
+ RuoteAMQP::LaunchitemListener.new(@engine)
20
29
 
21
- MQ.queue( RuoteAMQP::LaunchitemListener.queue ).publish( json )
30
+ MQ.queue('ruote_launchitems', :durable => true).publish(json)
22
31
 
23
32
  sleep 0.5
24
33
 
25
34
  @engine.should_not have_errors
26
35
  @engine.should_not have_remaining_expressions
27
36
 
28
- @tracer.to_s.should == "bar"
37
+ @tracer.to_s.should == 'bar'
38
+ end
29
39
 
30
- purge_engine
40
+ it 'discards corrupt process definitions' do
41
+
42
+ json = {
43
+ 'definition' => %{
44
+ I'm a broken process definition
45
+ },
46
+ 'fields' => { 'foo' => 'bar' }
47
+ }.to_json
48
+
49
+ RuoteAMQP::LaunchitemListener.new(@engine, :unsubscribe => true)
50
+
51
+ serr = String.new
52
+ err = StringIO.new(serr, 'w+')
53
+ $stderr = err
54
+
55
+ MQ.queue('ruote_launchitems', :durable => true).publish(json)
56
+
57
+ sleep 0.5
58
+
59
+ err.close
60
+ $stderr = STDERR
61
+
62
+ @engine.should_not have_errors
63
+ @engine.should_not have_remaining_expressions
64
+
65
+ @tracer.to_s.should == ''
66
+
67
+ serr.should match(/^===/)
31
68
  end
32
69
  end
70
+
@@ -1,26 +1,28 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
1
 
3
- describe RuoteAMQP::Participant, :type => :ruote do
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
4
3
 
5
- it "should support 'reply anyway' on expression parameter" do
4
+
5
+ describe RuoteAMQP::ParticipantProxy, :type => :ruote do
6
+
7
+ it "supports 'forget' as participant attribute" do
6
8
 
7
9
  pdef = ::Ruote.process_definition :name => 'test' do
8
10
  sequence do
9
- amqp :queue => 'test1', 'reply_anyway' => true
11
+ amqp :queue => 'test1', :forget => true
10
12
  echo 'done.'
11
13
  end
12
14
  end
13
15
 
14
- @engine.register_participant( :amqp, RuoteAMQP::Participant )
16
+ @engine.register_participant(:amqp, RuoteAMQP::ParticipantProxy)
15
17
 
16
- run_definition( pdef )
18
+ run_definition(pdef)
17
19
 
18
20
  @tracer.to_s.should == 'done.'
19
21
 
20
22
  begin
21
23
  Timeout::timeout(10) do
22
24
  @msg = nil
23
- MQ.queue('test1').subscribe { |msg| @msg = msg }
25
+ MQ.queue('test1', :durable => true).subscribe { |msg| @msg = msg }
24
26
 
25
27
  loop do
26
28
  break unless @msg.nil?
@@ -34,7 +36,7 @@ describe RuoteAMQP::Participant, :type => :ruote do
34
36
  @msg.should match(/^\{.*\}$/) # JSON message by default
35
37
  end
36
38
 
37
- it "should support 'reply anyway' as participant configuration" do
39
+ it "supports 'forget' as participant option" do
38
40
 
39
41
  pdef = ::Ruote.process_definition :name => 'test' do
40
42
  sequence do
@@ -43,17 +45,17 @@ describe RuoteAMQP::Participant, :type => :ruote do
43
45
  end
44
46
  end
45
47
 
46
- p = RuoteAMQP::Participant.new( :reply_by_default => true )
47
- @engine.register_participant( :amqp, p )
48
+ @engine.register_participant(
49
+ :amqp, RuoteAMQP::ParticipantProxy, 'forget' => true)
48
50
 
49
- run_definition( pdef )
51
+ run_definition(pdef)
50
52
 
51
53
  @tracer.to_s.should == "done."
52
54
 
53
55
  begin
54
56
  Timeout::timeout(5) do
55
57
  @msg = nil
56
- MQ.queue('test4').subscribe { |msg| @msg = msg }
58
+ MQ.queue('test4', :durable => true).subscribe { |msg| @msg = msg }
57
59
 
58
60
  loop do
59
61
  break unless @msg.nil?
@@ -64,28 +66,28 @@ describe RuoteAMQP::Participant, :type => :ruote do
64
66
  violated "Timeout waiting for message"
65
67
  end
66
68
 
67
- @msg.should match( /^\{.*\}$/) # JSON message by default
69
+ @msg.should match(/^\{.*\}$/) # JSON message by default
68
70
  end
69
71
 
70
- it "should support custom messages instead of workitems" do
72
+ it "supports custom messages instead of workitems" do
71
73
 
72
74
  pdef = ::Ruote.process_definition :name => 'test' do
73
75
  sequence do
74
- amqp :queue => 'test2', :message => 'foo', 'reply_anyway' => true
76
+ amqp :queue => 'test2', :message => 'foo', :forget => true
75
77
  echo 'done.'
76
78
  end
77
79
  end
78
80
 
79
- @engine.register_participant( :amqp, RuoteAMQP::Participant )
81
+ @engine.register_participant(:amqp, RuoteAMQP::ParticipantProxy)
80
82
 
81
- run_definition( pdef )
83
+ run_definition(pdef)
82
84
 
83
85
  @tracer.to_s.should == "done."
84
86
 
85
87
  begin
86
88
  Timeout::timeout(5) do
87
89
  @msg = nil
88
- MQ.queue('test2').subscribe { |msg| @msg = msg }
90
+ MQ.queue('test2', :durable => true).subscribe { |msg| @msg = msg }
89
91
 
90
92
  loop do
91
93
  break unless @msg.nil?
@@ -99,26 +101,26 @@ describe RuoteAMQP::Participant, :type => :ruote do
99
101
  @msg.should == 'foo'
100
102
  end
101
103
 
102
- it "should support a default queue name" do
104
+ it "supports 'queue' as a participant option" do
103
105
 
104
106
  pdef = ::Ruote.process_definition :name => 'test' do
105
107
  sequence do
106
- amqp 'reply_anyway' => true
108
+ amqp :forget => true
107
109
  echo 'done.'
108
110
  end
109
111
  end
110
112
 
111
- amqp = RuoteAMQP::Participant.new( :default_queue => 'test5' )
112
- @engine.register_participant( :amqp, amqp )
113
+ @engine.register_participant(
114
+ :amqp, RuoteAMQP::ParticipantProxy, 'queue' => 'test5')
113
115
 
114
- run_definition( pdef )
116
+ run_definition(pdef)
115
117
 
116
118
  @tracer.to_s.should == 'done.'
117
119
 
118
120
  begin
119
121
  Timeout::timeout(5) do
120
122
  @msg = nil
121
- MQ.queue('test5').subscribe { |msg| @msg = msg }
123
+ MQ.queue('test5', :durable => true).subscribe { |msg| @msg = msg }
122
124
 
123
125
  loop do
124
126
  break unless @msg.nil?
@@ -130,42 +132,61 @@ describe RuoteAMQP::Participant, :type => :ruote do
130
132
  end
131
133
  end
132
134
 
133
- it "should support mapping participant names to queue names" do
135
+ it "passes 'participant_options' over amqp" do
134
136
 
135
137
  pdef = ::Ruote.process_definition :name => 'test' do
136
- sequence do
137
- q1
138
- q2
139
- amqp
140
- echo 'done.'
141
- end
138
+ amqp :queue => 'test6', :forget => true
142
139
  end
143
140
 
144
- amqp = RuoteAMQP::Participant.new( :reply_by_default => true, :default_queue => 'test6' )
145
- amqp.map_participant( 'q1', 'test7' )
146
- amqp.map_participant( 'q2', 'test8' )
147
- @engine.register_participant( :amqp, amqp )
148
- @engine.register_participant( :q1, amqp )
149
- @engine.register_participant( :q2, amqp )
141
+ @engine.register_participant(:amqp, RuoteAMQP::ParticipantProxy)
150
142
 
151
- run_definition( pdef )
143
+ run_definition(pdef)
152
144
 
153
- @tracer.to_s.should == 'done.'
145
+ msg = nil
154
146
 
155
- [ 'test6', 'test7', 'test8' ].each do |q|
156
- begin
157
- Timeout::timeout(5) do
158
- @msg = nil
159
- MQ.queue( q ).subscribe { |msg| @msg = msg }
147
+ begin
148
+ Timeout::timeout(10) do
160
149
 
161
- loop do
162
- break unless @msg.nil?
163
- sleep 0.1
164
- end
150
+ MQ.queue('test6', :durable => true).subscribe { |m| msg = m }
151
+
152
+ loop do
153
+ break unless msg.nil?
154
+ sleep 0.1
165
155
  end
166
- rescue Timeout::Error
167
- violated "Timeout waiting for message on #{q}"
168
156
  end
157
+ rescue Timeout::Error
158
+ violated "Timeout waiting for message"
169
159
  end
160
+
161
+ wi = Rufus::Json.decode(msg)
162
+ params = wi['fields']['params']
163
+
164
+ params['queue'].should == 'test6'
165
+ params['forget'].should == true
166
+ params['participant_options'].should == { 'forget' => false, 'queue' => nil }
167
+ end
168
+
169
+ it "doesn't create 1 queue instance per delivery" do
170
+
171
+ pdef = ::Ruote.process_definition do
172
+ amqp :queue => 'test7', :forget => true
173
+ end
174
+
175
+ mq_count = 0
176
+ ObjectSpace.each_object(MQ) { |o| mq_count += 1 }
177
+
178
+ @engine.register_participant(:amqp, RuoteAMQP::ParticipantProxy)
179
+
180
+ 10.times do
181
+ run_definition(pdef)
182
+ end
183
+
184
+ sleep 1
185
+
186
+ count = 0
187
+ ObjectSpace.each_object(MQ) { |o| count += 1 }
188
+
189
+ count.should == mq_count + 1
170
190
  end
171
191
  end
192
+