propono 0.11.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8193ab42cb462b70c436b83459abb2df0203f6be
4
- data.tar.gz: 225ec5fc62711fee56fe6520ba4bd745fb8a5aca
3
+ metadata.gz: 86ff2b93684d9aa043023ff05f81ccebcc6e108c
4
+ data.tar.gz: 7da5b3c1461cb3602e56dff51bcd97343e776262
5
5
  SHA512:
6
- metadata.gz: 75c925d5b89a45630c70e9eb35e558db179eb77caf8a6c243dd9ce827da1e390e825a8205f5df9563e5ab5ab1b510f2d31fea1a0683188cdfdc64e85d102e51e
7
- data.tar.gz: 7ccad01b84956d6a6a3dc22475fc26b0a839f7de0a1a866c655d06da75d300116d2f48c658f3821dc4d804441058c07e79ab3fbe5467abe90738951fc20aeaa6
6
+ metadata.gz: 38bd2565e6e0a0b9eb328d329b8d890ed13def10331792b5dcae86eafbc8535143faf7734a3607f45290ace56e1fc72d8e7559ed4d894348bb66aec12636758b
7
+ data.tar.gz: f2f63928f0f4b565636abf7504706b08dfead8cbe0d71b75946289824ff79992bae05bb526ff5d3e977da3735fec7577a7952d3ab5fb7391aa131d3885f4e49a
data/CHANGELOG.md CHANGED
@@ -1,47 +1,39 @@
1
- # 0.11.1 / 2013-12-09
1
+ # 1.0.0.rc1 / 2013-12-15
2
+ * [FEATURE] Improve transactional handling of messages.
3
+ * [FEATURE] Add failed/corrupt queues.
2
4
 
5
+ # 0.11.1 / 2013-12-09
3
6
  * [BUGFIX] Re raise 403 forbidden excetion instead of continuing.
4
7
 
5
8
  # 0.11.0 / 2013-12-03
6
-
7
9
  * [FEATURE] Add support for IAM profiles for AWS auth
8
10
 
9
11
  # 0.10.0 / 2013-12-03
10
-
11
12
  * [FEATURE] Add queue_suffix config variable
12
13
 
13
14
  # 0.9.1 / Unreleased
14
-
15
15
  * [FEATURE] Propono will raise exceptions if the message processing fails
16
16
 
17
17
  # 0.9.0 / Unreleased
18
-
19
18
  * [FEATURE] Add message ids that track throughout Propono
20
19
 
21
20
  # 0.8.2 / 2013-11-01
22
-
23
21
  * [BUGFIX] Replace thread library with standard ruby threads to fix Unicorn problems.
24
22
 
25
23
  # 0.8.1 / 2013-11-01
26
-
27
24
  * [FEATURE] Log all messages published from Propono.
28
25
 
29
26
  # 0.8.0 / 2013-11-01
30
-
31
27
  * [FEATURE] SNS publish now delegates to a thread pool. The SNS response can be accessed via a future.
32
28
 
33
29
  # 0.7.0 / 2013-10-23
34
-
35
30
  * [FEATURE] Add TCP publish and listen methods.
36
31
 
37
32
  # 0.6.3 / 2013-10-20
38
-
39
33
  * [FEATURE] Catch all StandardError exceptions for UDP publishes.
40
34
 
41
35
  # 0.6.2 / 2013-10-20
42
-
43
36
  * [BUGFIX] Fixed integration tests that sometimes failed due to shared UDP ports or slow SQS subscriptions.
44
37
 
45
38
  # 0.6.1 / 2013-10-20
46
-
47
39
  * [BUGFIX] Added `require 'json'` to udp_listener.rb
data/lib/propono.rb CHANGED
@@ -15,6 +15,7 @@ require "propono/components/queue"
15
15
  require "propono/components/topic"
16
16
  require "propono/components/post_subscription"
17
17
  require "propono/components/queue_subscription"
18
+ require "propono/components/sqs_message"
18
19
 
19
20
  require "propono/services/publisher"
20
21
  require "propono/services/queue_creator"
@@ -4,17 +4,18 @@ module Propono
4
4
  include Sns
5
5
  include Sqs
6
6
 
7
- attr_reader :topic_arn, :queue
7
+ attr_reader :topic_arn, :queue_name, :queue
8
8
 
9
- def self.create(topic_id)
10
- new(topic_id).tap do |subscription|
9
+ def self.create(topic_id, options = {})
10
+ new(topic_id, options).tap do |subscription|
11
11
  subscription.create
12
12
  end
13
13
  end
14
14
 
15
- def initialize(topic_id)
15
+ def initialize(topic_id, options = {})
16
16
  @topic_id = topic_id
17
17
  @suffixed_topic_id = "#{topic_id}#{Propono.config.queue_suffix}"
18
+ @queue_name = "#{Propono.config.application_name.gsub(" ", "_")}-#{@suffixed_topic_id}#{options[:queue_name_suffix]}"
18
19
  end
19
20
 
20
21
  def create
@@ -25,10 +26,6 @@ module Propono
25
26
  sqs.set_queue_attributes(@queue.url, "Policy", generate_policy)
26
27
  end
27
28
 
28
- def queue_name
29
- @queue_name ||= "#{Propono.config.application_name.gsub(" ", "_")}-#{@suffixed_topic_id}"
30
- end
31
-
32
29
  private
33
30
 
34
31
  def generate_policy
@@ -5,9 +5,7 @@ module Propono
5
5
  private
6
6
 
7
7
  def sns
8
- @sns ||= Fog::AWS::SNS.new(
9
- Propono.aws_options
10
- )
8
+ @sns ||= Fog::AWS::SNS.new(Propono.aws_options)
11
9
  end
12
10
  end
13
11
  end
@@ -5,9 +5,7 @@ module Propono
5
5
  private
6
6
 
7
7
  def sqs
8
- @sqs ||= Fog::AWS::SQS.new(
9
- Propono.aws_options
10
- )
8
+ @sqs ||= Fog::AWS::SQS.new(Propono.aws_options)
11
9
  end
12
10
  end
13
11
  end
@@ -0,0 +1,20 @@
1
+ module Propono
2
+ class SqsMessage
3
+ include Sqs
4
+
5
+ attr_reader :context, :message, :raw_message, :receipt_handle
6
+ def initialize(raw_message)
7
+ body = JSON.parse(raw_message["Body"])["Message"]
8
+ body = JSON.parse(body)
9
+
10
+ @raw_message = raw_message
11
+ @context = body.symbolize_keys
12
+ @message = context.delete(:message)
13
+ @receipt_handle = raw_message["receipt_handle"]
14
+ end
15
+
16
+ def ==(other)
17
+ other.is_a?(SqsMessage) && other.receipt_handle == @receipt_handle
18
+ end
19
+ end
20
+ end
@@ -1,6 +1,5 @@
1
1
  module Propono
2
2
  class QueueListener
3
-
4
3
  include Sqs
5
4
 
6
5
  def self.listen(topic_id, &message_processor)
@@ -29,10 +28,10 @@ module Propono
29
28
  if messages.empty?
30
29
  false
31
30
  else
32
- messages.each { |msg| process_sqs_message(msg) }
31
+ messages.each { |msg| process_raw_message(msg) }
33
32
  end
34
33
  rescue Excon::Errors::Forbidden
35
- Propono.config.logger.error "Forbidden error caught and re raised. #{queue_url}"
34
+ Propono.config.logger.error "Forbidden error caught and re-raised. #{queue_url}"
36
35
  Propono.config.logger.error $!
37
36
  raise $!
38
37
  rescue
@@ -40,22 +39,44 @@ module Propono
40
39
  Propono.config.logger.error $!
41
40
  end
42
41
 
43
- def process_sqs_message(sqs_message)
44
- body = JSON.parse(sqs_message["Body"])["Message"]
45
-
46
- # Legacy syntax is covered in the rescue statement
47
- # This begin/rescue dance and the rescue block will be removed in v1.
42
+ # The calls to delete_message are deliberately duplicated so
43
+ # as to ensure the message is only deleted if the preceeding line
44
+ # has completed succesfully. We do *not* want to ensure that the
45
+ # message is deleted regardless of what happens in this method.
46
+ def process_raw_message(raw_sqs_message)
48
47
  begin
49
- body = JSON.parse(body)
50
- context = body.symbolize_keys
51
- message = context.delete(:message)
52
- Propono.config.logger.info "Propono [#{context[:id]}]: Received from sqs."
53
- @message_processor.call(message, context)
54
- rescue JSON::ParserError, TypeError
55
- Propono.config.logger.info("Sending and recieving messages without ids is deprecated")
56
- @message_processor.call(body)
48
+ sqs_message = SqsMessage.new(raw_sqs_message)
49
+ Propono.config.logger.info "Propono [#{sqs_message.context[:id]}]: Received from sqs."
50
+
51
+ begin
52
+ process_message(sqs_message)
53
+ delete_message(raw_sqs_message)
54
+ rescue
55
+ move_to_failed_queue(sqs_message)
56
+ delete_message(raw_sqs_message)
57
+ end
58
+ rescue
59
+ move_to_corrupt_queue(raw_sqs_message)
60
+ delete_message(raw_sqs_message)
57
61
  end
58
- sqs.delete_message(queue_url, sqs_message['ReceiptHandle'])
62
+ end
63
+
64
+ def process_message(sqs_message)
65
+ @message_processor.call(sqs_message.message, sqs_message.context)
66
+ end
67
+
68
+ def move_to_corrupt_queue(raw_sqs_message)
69
+ QueueSubscription.create(@topic_id, queue_name_suffix: "-corrupt")
70
+ Propono.publish("#{@topic_id}-corrupt", raw_sqs_message)
71
+ end
72
+
73
+ def move_to_failed_queue(sqs_message)
74
+ QueueSubscription.create(@topic_id, queue_name_suffix: "-failed")
75
+ Propono.publish("#{@topic_id}-failed", sqs_message.message, id: sqs_message.context[:id])
76
+ end
77
+
78
+ def delete_message(raw_sqs_message)
79
+ sqs.delete_message(queue_url, raw_sqs_message['ReceiptHandle'])
59
80
  end
60
81
 
61
82
  def queue_url
@@ -1,3 +1,3 @@
1
1
  module Propono
2
- VERSION = "0.11.1"
2
+ VERSION = "1.0.0.rc1"
3
3
  end
@@ -16,9 +16,9 @@ module Propono
16
16
  end
17
17
  end
18
18
 
19
- # Wait a max of 30secs before failing the test
19
+ # Wait a max of 20secs before failing the test
20
20
  def wait_for_thread(thread)
21
- 300.times do |x|
21
+ 200.times do |x|
22
22
  return true unless thread.alive?
23
23
  sleep(0.1)
24
24
  end
@@ -4,8 +4,9 @@ module Propono
4
4
  class SnsToSqsTest < IntegrationTest
5
5
  def test_the_message_gets_there
6
6
  topic = "test-topic"
7
- text = "This is my message"
7
+ text = "This is my message #{DateTime.now} #{rand()}"
8
8
  flunks = []
9
+ message_received = false
9
10
 
10
11
  Propono.subscribe_by_queue(topic)
11
12
 
@@ -14,7 +15,7 @@ module Propono
14
15
  Propono.listen_to_queue(topic) do |message, context|
15
16
  flunks << "Wrong message" unless message == text
16
17
  flunks << "Wrong id" unless context[:id] =~ Regexp.new("[a-z0-9]{6}")
17
- break
18
+ message_received = true
18
19
  end
19
20
  rescue => e
20
21
  flunks << e.message
@@ -23,7 +24,13 @@ module Propono
23
24
  end
24
25
  end
25
26
 
26
- sleep(2) # Make sure the listener has started
27
+ Thread.new do
28
+ sleep(1) while !message_received
29
+ sleep(5) # Make sure all the message deletion clear up in the thread has happened
30
+ thread.terminate
31
+ end
32
+
33
+ sleep(1) # Make sure the listener has started
27
34
 
28
35
  Propono.publish(topic, text)
29
36
  flunks << "Test Timeout" unless wait_for_thread(thread)
@@ -4,8 +4,9 @@ module Propono
4
4
  class UdpToSqsTest < IntegrationTest
5
5
  def test_the_message_gets_there
6
6
  topic = "test-topic"
7
- message = "This is my message"
7
+ message = "This is my message #{DateTime.now} #{rand()}"
8
8
  flunks = []
9
+ message_received = false
9
10
 
10
11
  Propono.config.tcp_host = "localhost"
11
12
  Propono.config.tcp_port = 20009
@@ -17,6 +18,7 @@ module Propono
17
18
  Propono.listen_to_queue(topic) do |sqs_message|
18
19
  flunks << "Wrong message" unless message == sqs_message
19
20
  sqs_thread.terminate
21
+ message_received = true
20
22
  end
21
23
  rescue => e
22
24
  flunks << e.message
@@ -25,13 +27,19 @@ module Propono
25
27
  end
26
28
  end
27
29
 
30
+ Thread.new do
31
+ sleep(1) while !message_received
32
+ sleep(5) # Make sure all the message deletion clear up in the thread has happened
33
+ sqs_thread.terminate
34
+ end
35
+
28
36
  tcp_thread = Thread.new do
29
37
  Propono.listen_to_tcp do |tcp_topic, tcp_message|
30
38
  Propono.publish(tcp_topic, tcp_message)
31
39
  tcp_thread.terminate
32
40
  end
33
41
  end
34
- sleep(2) # Make sure the listener has started
42
+ sleep(1) # Make sure the listener has started
35
43
 
36
44
  Propono.publish(topic, message, protocol: :tcp)
37
45
  flunks << "Test Timeout" unless wait_for_thread(tcp_thread) && wait_for_thread(sqs_thread)
@@ -4,8 +4,9 @@ module Propono
4
4
  class UdpProxyTest < IntegrationTest
5
5
  def test_the_message_gets_there
6
6
  topic = "test-topic"
7
- text = "This is my message"
7
+ text = "This is my message #{DateTime.now} #{rand()}"
8
8
  flunks = []
9
+ message_received = false
9
10
 
10
11
  Propono.config.udp_port = 20001
11
12
 
@@ -16,7 +17,7 @@ module Propono
16
17
  Propono.listen_to_queue(topic) do |message, context|
17
18
  flunks << "Wrong message" unless text == message
18
19
  flunks << "Wrong id" unless context[:id] =~ Regexp.new("[a-z0-9]{6}-[a-z0-9]{6}")
19
- break
20
+ message_received = true
20
21
  end
21
22
  rescue => e
22
23
  flunks << e.message
@@ -25,11 +26,17 @@ module Propono
25
26
  end
26
27
  end
27
28
 
29
+ Thread.new do
30
+ sleep(1) while !message_received
31
+ sleep(5) # Make sure all the message deletion clear up in the thread has happened
32
+ sqs_thread.terminate
33
+ end
34
+
28
35
  udp_thread = Thread.new do
29
36
  Propono.proxy_udp
30
37
  end
31
38
 
32
- sleep(2) # Make sure the proxy has started
39
+ sleep(1) # Make sure the proxy has started
33
40
 
34
41
  Propono::Publisher.publish(topic, text, protocol: :udp)
35
42
  flunks << "Test timeout" unless wait_for_thread(sqs_thread)
@@ -4,8 +4,9 @@ module Propono
4
4
  class UdpToSqsTest < IntegrationTest
5
5
  def test_the_message_gets_there
6
6
  topic = "test-topic"
7
- message = "This is my message"
7
+ message = "This is my message #{DateTime.now} #{rand()}"
8
8
  flunks = []
9
+ message_received = false
9
10
 
10
11
  Propono.config.udp_port = 20002
11
12
 
@@ -16,6 +17,7 @@ module Propono
16
17
  Propono.listen_to_queue(topic) do |sqs_message|
17
18
  assert_equal message, sqs_message
18
19
  sqs_thread.terminate
20
+ message_received = true
19
21
  end
20
22
  rescue => e
21
23
  flunks << e.message
@@ -24,6 +26,12 @@ module Propono
24
26
  end
25
27
  end
26
28
 
29
+ Thread.new do
30
+ sleep(1) while !message_received
31
+ sleep(5) # Make sure all the message deletion clear up in the thread has happened
32
+ sqs_thread.terminate
33
+ end
34
+
27
35
  udp_thread = Thread.new do
28
36
  Propono.listen_to_udp do |udp_topic, udp_message|
29
37
  Propono.publish(udp_topic, udp_message)
@@ -31,7 +39,7 @@ module Propono
31
39
  end
32
40
  end
33
41
 
34
- sleep(2) # Make sure the listener has started
42
+ sleep(1) # Make sure the listener has started
35
43
 
36
44
  Propono.publish(topic, message, protocol: :udp)
37
45
  flunks << "Test Timeout" unless wait_for_thread(udp_thread) && wait_for_thread(sqs_thread)
@@ -57,14 +57,14 @@ module Propono
57
57
  end
58
58
 
59
59
  def test_read_messages_calls_process_message_for_each_msg
60
- @listener.expects(:process_sqs_message).with(@sqs_message1)
61
- @listener.expects(:process_sqs_message).with(@sqs_message2)
60
+ @listener.expects(:process_raw_message).with(@sqs_message1)
61
+ @listener.expects(:process_raw_message).with(@sqs_message2)
62
62
  @listener.send(:read_messages)
63
63
  end
64
64
 
65
65
  def test_read_messages_does_not_call_process_messages_if_there_are_none
66
66
  @sqs_response.stubs(body: {"Message" => []})
67
- @listener.expects(:process_sqs_message).never
67
+ @listener.expects(:process_message).never
68
68
  @listener.send(:read_messages)
69
69
  end
70
70
 
@@ -79,7 +79,7 @@ module Propono
79
79
  def test_forbidden_error_is_logged_and_re_raised
80
80
  @listener.stubs(queue_url: "http://example.com")
81
81
  @sqs.stubs(:receive_message).raises(Excon::Errors::Forbidden.new(nil, nil, nil))
82
- Propono.config.logger.expects(:error).with("Forbidden error caught and re raised. http://example.com")
82
+ Propono.config.logger.expects(:error).with("Forbidden error caught and re-raised. http://example.com")
83
83
  Propono.config.logger.expects(:error).with() {|x| x.is_a?(Excon::Errors::Forbidden)}
84
84
  assert_raises Excon::Errors::Forbidden do
85
85
  @listener.send(:read_messages)
@@ -123,50 +123,47 @@ module Propono
123
123
  @listener.send(:read_messages)
124
124
  end
125
125
 
126
- def test_messages_are_not_deleted_if_there_is_an_exception
126
+ def test_messages_are_deleted_if_there_is_an_exception_processing
127
+ queue_url = "test-queue-url"
128
+
129
+ @sqs.expects(:delete_message).with(queue_url, @receipt_handle1)
130
+ @sqs.expects(:delete_message).with(queue_url, @receipt_handle2)
131
+
127
132
  @listener = QueueListener.new(@topic_id) { raise StandardError.new("Test Error") }
133
+ @listener.stubs(queue_url: queue_url)
128
134
  @listener.stubs(sqs: @sqs)
129
- @sqs.expects(:delete_message).never
130
135
  @listener.send(:read_messages)
131
136
  end
132
- end
133
- class QueueListenerLegacySyntaxTest < Minitest::Test
134
-
135
- def setup
136
- super
137
- @topic_id = "some-topic"
138
-
139
- @receipt_handle1 = "test-receipt-handle1"
140
- @receipt_handle2 = "test-receipt-handle2"
141
- @message1 = {'cat' => "Foobar 123"}
142
- @message2 = "qwertyuiop"
143
- @sqs_message1 = { "ReceiptHandle" => @receipt_handle1, "Body" => {"Message" => @message1}.to_json}
144
- @sqs_message2 = { "ReceiptHandle" => @receipt_handle2, "Body" => {"Message" => @message2}.to_json}
145
- @messages = { "Message" => [ @sqs_message1, @sqs_message2 ] }
146
- @sqs_response = mock().tap{|m|m.stubs(body: @messages)}
147
- @sqs = mock()
148
- @sqs.stubs(receive_message: @sqs_response)
149
- @sqs.stubs(:delete_message)
150
137
 
151
- @listener = QueueListener.new(@topic_id) {}
138
+ def test_messages_are_moved_to_failed_queue_if_there_is_an_exception
139
+ @listener = QueueListener.new(@topic_id) { raise StandardError.new("Test Error") }
140
+ @listener.expects(:move_to_failed_queue).with(SqsMessage.new(@sqs_message1))
141
+ @listener.expects(:move_to_failed_queue).with(SqsMessage.new(@sqs_message2))
152
142
  @listener.stubs(sqs: @sqs)
143
+ @listener.send(:read_messages)
153
144
  end
154
145
 
155
- def test_old_syntax_has_deprecation_warning
156
- Propono.config.logger.expects(:info).with("Sending and recieving messages without ids is deprecated")
157
- @listener.stubs(sqs: @sqs)
146
+ def test_messages_are_moved_to_corrupt_queue_if_there_is_an_parsing_exception
147
+ sqs_message1 = "foobar"
148
+ sqs_message2 = "barfoo"
149
+ @messages["Message"][0] = sqs_message1
150
+ @messages["Message"][1] = sqs_message2
151
+
152
+ @listener.expects(:move_to_corrupt_queue).with(sqs_message1)
153
+ @listener.expects(:move_to_corrupt_queue).with(sqs_message2)
158
154
  @listener.send(:read_messages)
159
155
  end
160
156
 
161
- def test_each_message_processor_is_yielded
162
- messages_yielded = []
163
- @listener = QueueListener.new(@topic_id) { |m| messages_yielded.push(m) }
164
- @listener.stubs(sqs: @sqs)
165
- @listener.send(:read_messages)
157
+ def test_move_to_failed_queue
158
+ QueueSubscription.expects(:create).with(@topic_id, queue_name_suffix: "-failed")
159
+ Propono.expects(:publish).with("#{@topic_id}-failed", @message1, id: @message1_id)
160
+ @listener.send(:move_to_failed_queue, SqsMessage.new(@sqs_message1))
161
+ end
166
162
 
167
- assert_equal messages_yielded.size, 2
168
- assert messages_yielded.include?(@message1)
169
- assert messages_yielded.include?(@message2)
163
+ def test_move_to_corrupt_queue
164
+ QueueSubscription.expects(:create).with(@topic_id, queue_name_suffix: "-corrupt")
165
+ Propono.expects(:publish).with("#{@topic_id}-corrupt", @sqs_message1)
166
+ @listener.send(:move_to_corrupt_queue, @sqs_message1)
170
167
  end
171
168
  end
172
169
  end
@@ -5,7 +5,7 @@ module Propono
5
5
 
6
6
  def test_subscribe_by_queue_calls_queue_subscriber
7
7
  subscriber = QueueSubscription.new("topic")
8
- QueueSubscription.expects(:new).with("topic").returns(subscriber)
8
+ QueueSubscription.expects(:new).with("topic", {}).returns(subscriber)
9
9
  QueueSubscription.any_instance.expects(:create)
10
10
  Subscriber.subscribe_by_queue("topic")
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: propono
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 1.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MalcyL
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-09 00:00:00.000000000 Z
12
+ date: 2013-12-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
@@ -118,6 +118,7 @@ files:
118
118
  - lib/propono/components/queue_subscription.rb
119
119
  - lib/propono/components/sns.rb
120
120
  - lib/propono/components/sqs.rb
121
+ - lib/propono/components/sqs_message.rb
121
122
  - lib/propono/components/topic.rb
122
123
  - lib/propono/configuration.rb
123
124
  - lib/propono/helpers/hash.rb
@@ -172,9 +173,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
172
173
  version: '0'
173
174
  required_rubygems_version: !ruby/object:Gem::Requirement
174
175
  requirements:
175
- - - '>='
176
+ - - '>'
176
177
  - !ruby/object:Gem::Version
177
- version: '0'
178
+ version: 1.3.1
178
179
  requirements: []
179
180
  rubyforge_project:
180
181
  rubygems_version: 2.0.3