ruote-amqp 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+