klomp 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,11 @@
1
1
  Changes
2
2
  --------------------------------------------------------------------------------
3
3
 
4
+ 0.0.5 (2012/8/10)
5
+ ================================================================================
6
+
7
+ - set message send/receive logging to debug instead of info
8
+
4
9
  0.0.4 (2012/6/22)
5
10
  ================================================================================
6
11
 
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source "http://rubygems.org"
2
2
 
3
3
  gem "onstomp", "~> 1.0.7"
4
4
  # gem 'onstomp', :path => '../onstomp'
5
+ gem "stomp", "~> 1.2.4"
5
6
  gem "json"
6
7
  gem "uuid", "~> 2.3.5"
7
8
 
@@ -9,6 +9,7 @@ GEM
9
9
  systemu (~> 2.5.0)
10
10
  onstomp (1.0.7)
11
11
  rake (0.9.2.2)
12
+ stomp (1.2.4)
12
13
  systemu (2.5.1)
13
14
  thor (0.15.2)
14
15
  uuid (2.3.5)
@@ -23,4 +24,5 @@ DEPENDENCIES
23
24
  json
24
25
  onstomp (~> 1.0.7)
25
26
  rake
27
+ stomp (~> 1.2.4)
26
28
  uuid (~> 2.3.5)
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Klomp
2
2
 
3
- Klomp is a simple wrapper around the [OnStomp](https://github.com/meadvillerb/onstomp/)
4
- library with some additional HA and usability features:
3
+ Klomp is a simple wrapper around the [Stomp](https://github.com/stompgem/stomp)
4
+ and [OnStomp](https://github.com/meadvillerb/onstomp/) libraries with some
5
+ additional HA and usability features:
5
6
 
6
7
  * When initialized with multiple broker URIs, Klomp will publish messages to
7
8
  one broker at a time, but will consume from all brokers simultaneously. This is
8
- a slight improvement over the regular [OnStomp::Failover::Client](http://mdvlrb.com/onstomp/OnStomp/Failover/Client.html)
9
- which handles all publishing and subscribing through a single "active" broker.
10
- This traditional one-broker-at-a-time technique can lead to a split-brain
11
- scenario in which messages are only received by a subset of your STOMP clients.
12
- By consuming from all brokers simultaneously, Klomp ensures that no message is
13
- left behind.
9
+ a slight improvement over traditional failover clients that work by publishing
10
+ and subscribing through a single "active" broker. This one-broker-at-a-time
11
+ technique can lead to a split-brain scenario in which messages are only
12
+ received by a subset of your STOMP clients. By consuming from all brokers
13
+ simultaneously, Klomp ensures that no message is left behind.
14
14
 
15
15
  * Where applicable, message bodies are automatically translated between native
16
16
  Ruby and JSON objects.
@@ -26,14 +26,13 @@ subscribe block.
26
26
  ## Example usage
27
27
 
28
28
  The goal is that you should be able to use most (if not all) of the standard
29
- OnStomp API (see [OnStomp's UserNarrative](https://github.com/meadvillerb/onstomp/blob/master/extra_doc/UserNarrative.md))
30
- via a `Klomp::Client`:
29
+ Stomp/OnStomp APIs via a `Klomp::Client`:
31
30
 
32
31
  client = Klomp::Client.new([ ... ])
33
32
 
34
33
  However, there will be some differences in the API due to how `Klomp::Client`
35
- manages connections. For example, while the `connected?` method normally
36
- returns a boolean value, Klomp's `connected?` will return an array of booleans
34
+ manages connections. For example, while OnStomp's `connected?` method normally
35
+ returns a single boolean value, Klomp's `connected?` will return many booleans
37
36
  (i.e. one result for each broker).
38
37
 
39
38
  ### Fibonacci back-off retry behavior
@@ -59,6 +58,11 @@ pass implements a `#generate` method that returns a string ID.
59
58
  <th>Default value</th>
60
59
  <th>Description</th>
61
60
  </tr>
61
+ <tr>
62
+ <td>:adapter</td>
63
+ <td>:onstomp</td>
64
+ <td>Stomp library to use. Currently, only :stomp and :onstomp are supported.</td>
65
+ </tr>
62
66
  <tr>
63
67
  <td>:translate_json</td>
64
68
  <td>true</td>
@@ -75,9 +79,9 @@ pass implements a `#generate` method that returns a string ID.
75
79
  <td>Logger object</td>
76
80
  </tr>
77
81
  <tr>
78
- <td>:uuid</td>
79
- <td>UUID.new</td>
80
- <td>UUID generator object, responds to :generate and returns an ID</td>
82
+ <td>:uuid</td>
83
+ <td>UUID.new</td>
84
+ <td>UUID generator object, responds to :generate and returns an ID</td>
81
85
  </tr>
82
86
  </table>
83
87
 
@@ -102,9 +106,7 @@ failover.
102
106
 
103
107
  ## Change Log
104
108
 
105
- ### 0.0.1
106
-
107
- * Initial release
109
+ See the [ChangeLog file](/livingsocial/klomp/blob/master/ChangeLog.md).
108
110
 
109
111
  ## License
110
112
 
@@ -1,5 +1,5 @@
1
1
  require 'klomp/client'
2
2
 
3
3
  module Klomp
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
@@ -1,21 +1,50 @@
1
1
  require 'onstomp'
2
2
  require 'onstomp/failover'
3
+ require 'stomp'
3
4
  require 'json'
4
5
  require 'uuid'
5
6
  require 'logger'
7
+ require 'pp'
6
8
 
7
9
  class OnStomp::Failover::Client
8
10
  # Save previous N, N-1 delays for fibonacci backoff
9
11
  attr_accessor :prev_retry_delay
10
12
  end
11
13
 
14
+ class Hash
15
+ def to_query_string
16
+ self.inject([]) { |memo,(k,v)| memo + ["#{k}=#{v}"] }.join('&')
17
+ end
18
+ end
19
+
12
20
  module Klomp
13
21
 
14
22
  class Client
15
- attr_reader :read_conn, :write_conn
23
+ attr_reader :read_conn, :write_conn, :all_conn
16
24
  attr_accessor :last_connect_exception
17
25
 
26
+ WRITE_ONLY_METHODS = [
27
+ :abort,
28
+ :begin,
29
+ :commit,
30
+ ]
31
+
32
+ READ_ONLY_METHODS = [
33
+ :ack,
34
+ :nack,
35
+ ]
36
+
37
+ TRANSLATE_METHODS = {
38
+ :connected? => {
39
+ :stomp => :open?
40
+ },
41
+ :ack => {
42
+ :stomp => :acknowledge
43
+ }
44
+ }
45
+
18
46
  def initialize(uri, options={})
47
+ @adapter = options.fetch(:adapter, :onstomp)
19
48
  @translate_json = options.fetch(:translate_json, true)
20
49
  @auto_reply_to = options.fetch(:auto_reply_to, true)
21
50
  @logger = options.fetch(:logger, nil)
@@ -27,44 +56,91 @@ module Klomp
27
56
  options[:retry_delay] ||= 1
28
57
  options[:retry_attempts] ||= -1
29
58
 
30
- if uri.is_a?(Array)
31
- @write_conn = OnStomp::Failover::Client.new(uri, options)
32
- @read_conn = uri.map {|obj| OnStomp::Failover::Client.new([obj], options) }
59
+ case @adapter
60
+ when :onstomp
61
+ if uri.is_a?(Array)
62
+ @write_conn = OnStomp::Failover::Client.new(uri, options)
63
+ @read_conn = uri.map {|obj| OnStomp::Failover::Client.new([obj], options) }
64
+ else
65
+ @write_conn = OnStomp::Failover::Client.new([uri], options)
66
+ @read_conn = [@write_conn]
67
+ end
68
+ when :stomp
69
+ # Failover in the Stomp library is kind of flaky. If the client
70
+ # temporarily loses its connection, it is eventually able to reconnect
71
+ # and resume sending messages. However, the subscribe thread never
72
+ # seems to recover. One workaround I discovered is to always create new
73
+ # clients with the following URL scheme:
74
+ #
75
+ # failover:(stomp://localhost,stomp://localhost)
76
+ #
77
+ # Notice that we're using a failover URL in which the same host is
78
+ # specified twice. It's a pretty silly hack, but it's the only way I've
79
+ # been able to get failover to work reliably.
80
+ #
81
+ # - Mike Conigliaro
82
+ #
83
+ if uri.is_a?(Array)
84
+ @write_conn = Stomp::Client.new("failover:(#{uri.join(',')})?#{options.to_query_string}")
85
+ @read_conn = uri.map {|obj| Stomp::Client.new("failover:(#{obj},#{obj})?#{options.to_query_string}") }
86
+ else
87
+ @write_conn = Stomp::Client.new("failover:(#{uri},#{uri})?#{options.to_query_string}")
88
+ @read_conn = [@write_conn]
89
+ end
33
90
  else
34
- @write_conn = OnStomp::Failover::Client.new([uri], options)
35
- @read_conn = [@write_conn]
91
+ raise ArgumentError, "Klomp does not support the #{@adapter} library"
36
92
  end
37
93
  @all_conn = ([@write_conn] + @read_conn).uniq
38
94
  configure_connections
39
95
  end
40
96
 
41
97
  def connect
42
- @all_conn.each do |conn|
43
- begin
44
- attempts = conn.retry_attempts
45
- conn.retry_attempts = 1
46
- conn.connect
47
- rescue OnStomp::Failover::MaximumRetriesExceededError
48
- location = conn.active_client.uri.dup.tap {|u| u.password = 'REDACTED' }.to_s
49
- msg = ": #{last_connect_exception.message}" if last_connect_exception
50
- raise OnStomp::ConnectFailedError, "initial connection failed for #{location}#{msg}"
51
- ensure
52
- conn.retry_attempts = attempts
98
+ case @adapter
99
+ when :onstomp
100
+ @all_conn.each do |conn|
101
+ begin
102
+ attempts = conn.retry_attempts
103
+ conn.retry_attempts = 1
104
+ conn.connect
105
+ rescue OnStomp::Failover::MaximumRetriesExceededError
106
+ location = conn.active_client.uri.dup.tap {|u| u.password = 'REDACTED' }.to_s
107
+ msg = ": #{last_connect_exception.message}" if last_connect_exception
108
+ raise OnStomp::ConnectFailedError, "initial connection failed for #{location}#{msg}"
109
+ ensure
110
+ conn.retry_attempts = attempts
111
+ end
53
112
  end
54
113
  end
55
114
  self
56
115
  end
57
116
 
58
- def send(dest, body, headers={}, &cb)
117
+ def disconnect
118
+ method = case @adapter
119
+ when :onstomp
120
+ :disconnect
121
+ when :stomp
122
+ :close
123
+ end
124
+ @all_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method) }) }
125
+ end
126
+
127
+ def send(dest, body, headers={}, &block)
59
128
  if @translate_json && body.respond_to?(:to_json)
60
129
  body = body.to_json
61
- headers[:'content-type'] = 'application/json'
130
+ headers['content-type'] = 'application/json'
62
131
  else
63
132
  body = body.to_s
64
133
  end
65
- uuid = headers[:id] = @uuid.generate if @uuid
66
- log.info("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
67
- @write_conn.send(dest, body, headers, &cb)
134
+ uuid = headers['id'] = @uuid.generate if @uuid
135
+ log.debug("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
136
+
137
+ method = case @adapter
138
+ when :onstomp
139
+ :send
140
+ when :stomp
141
+ :publish
142
+ end
143
+ @write_conn.__send__(method, dest, body, headers, &block)
68
144
  end
69
145
  alias publish send
70
146
 
@@ -72,7 +148,7 @@ module Klomp
72
148
  frames = []
73
149
  @read_conn.each do |c|
74
150
  frames << c.subscribe(*args) do |msg|
75
- log.info("[Received] ID=#{msg[:id]} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
151
+ log.debug("[Received] ID=#{msg.headers['id']} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
76
152
  if @translate_json
77
153
  msg.body = begin
78
154
  JSON.parse(msg.body)
@@ -81,11 +157,11 @@ module Klomp
81
157
  end
82
158
  end
83
159
  reply_args = yield msg
84
- if @auto_reply_to && !msg.headers[:'reply-to'].nil?
160
+ if @auto_reply_to && !msg.headers['reply-to'].nil?
85
161
  if reply_args.is_a?(Array)
86
- send(msg.headers[:'reply-to'], *reply_args)
162
+ send(msg.headers['reply-to'], *reply_args)
87
163
  else
88
- send(msg.headers[:'reply-to'], reply_args)
164
+ send(msg.headers['reply-to'], reply_args)
89
165
  end
90
166
  end
91
167
  end
@@ -94,11 +170,16 @@ module Klomp
94
170
  end
95
171
 
96
172
  def unsubscribe(frames, headers={})
97
- if !frames.respond_to?(:length) || frames.length != @read_conn.length
98
- raise ArgumentError,
99
- "frames is not an array or its length does not match number of connections"
173
+ case @adapter
174
+ when :onstomp
175
+ if !frames.respond_to?(:length) || frames.length != @read_conn.length
176
+ raise ArgumentError,
177
+ "frames is not an array or its length does not match number of connections"
178
+ end
179
+ frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
180
+ else
181
+ @read_conn.each { |obj| obj.unsubscribe(frames, headers) }
100
182
  end
101
- frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
102
183
  end
103
184
 
104
185
  def subscriptions
@@ -109,47 +190,45 @@ module Klomp
109
190
  @logger
110
191
  end
111
192
 
112
- WRITE_ONLY_METHODS = [
113
- :abort,
114
- :begin,
115
- :commit,
116
- ]
117
-
118
- READ_ONLY_METHODS = [
119
- :ack,
120
- :nack,
121
- ]
122
-
123
193
  def method_missing(method, *args, &block)
194
+ if TRANSLATE_METHODS.has_key?(method) && TRANSLATE_METHODS[method].has_key?(@adapter)
195
+ method = TRANSLATE_METHODS[method][@adapter]
196
+ end
197
+
124
198
  case method
125
199
  when *WRITE_ONLY_METHODS
126
200
  @write_conn.__send__(method, *args, &block)
127
201
  when *READ_ONLY_METHODS
128
- @read_conn.map {|c| c.__send__(method, *args, &block) }
202
+ @read_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method, *args, &block) }) }
129
203
  else
130
- @all_conn.map {|c| c.__send__(method, *args) }
204
+ @all_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method, *args, &block) }) }
131
205
  end
132
206
  end
133
207
 
134
208
  private
209
+
135
210
  def configure_connections
136
- klomp_client = self
137
- @all_conn.each do |c|
138
- if @fib_retry_backoff
139
- c.before_failover_retry do |conn, attempt|
140
- if attempt == 1
141
- conn.prev_retry_delay, conn.retry_delay = 0, 1
142
- else
143
- conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
211
+ case @adapter
212
+ when :onstomp
213
+ klomp_client = self
214
+ @all_conn.each do |c|
215
+ if @fib_retry_backoff
216
+ c.before_failover_retry do |conn, attempt|
217
+ if attempt == 1
218
+ conn.prev_retry_delay, conn.retry_delay = 0, 1
219
+ else
220
+ conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
221
+ end
144
222
  end
145
223
  end
146
- end
147
224
 
148
- c.on_failover_connect_failure do
149
- klomp_client.last_connect_exception = $!
225
+ c.on_failover_connect_failure do
226
+ klomp_client.last_connect_exception = $!
227
+ end
150
228
  end
151
229
  end
152
230
  end
231
+
153
232
  end
154
233
 
155
234
  end
@@ -10,12 +10,12 @@ task :test_failover do
10
10
  client = Klomp::Client.new([
11
11
  'stomp://admin:password@localhost:61613',
12
12
  'stomp://admin:password@127.0.0.1:62613'
13
- ]).connect
13
+ ], :adapter => :onstomp).connect
14
14
 
15
15
  last_i = nil
16
16
  client.subscribe("/queue/test") do |msg|
17
17
  print "-"
18
- last_i = msg.body.to_i
18
+ last_i = msg.body.gsub('"', '').to_i
19
19
  end
20
20
 
21
21
  begin
@@ -0,0 +1,167 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @uris = [
13
+ 'stomp://admin:password@localhost:61613',
14
+ 'stomp://admin:password@127.0.0.1:62613'
15
+ ]
16
+ @destination = '/queue/test_component.test_event'
17
+ end
18
+
19
+ [ :onstomp, :stomp ].each do |adapter|
20
+
21
+ it "(#{adapter}) has a logger" do
22
+ logger = Logger.new(STDOUT)
23
+ client = Klomp::Client.new(@uris, :adapter => adapter, :logger => logger)
24
+ assert_equal client.log, logger
25
+ end
26
+
27
+ it "(#{adapter}) sends all unknown options through to the underlying library" do
28
+ client = Klomp::Client.new(@uris.first, :adapter => adapter, :haz_cheezburgers => true, :retry_attempts => 42).connect
29
+ assert client.connected?.values.all?
30
+ assert_equal 42, client.write_conn.retry_attempts if adapter == :onstomp
31
+ client.disconnect
32
+ end
33
+
34
+ it "(#{adapter}) accepts a single uri and establishes separate failover connections for writes and reads" do
35
+ client = Klomp::Client.new(@uris.first, :adapter => adapter).connect
36
+ assert_equal [client.write_conn], client.read_conn
37
+ assert client.connected?.values.all?
38
+ client.disconnect
39
+ end
40
+
41
+ it "(#{adapter}) accepts an array of uris and establishes separate failover connections for writes and reads" do
42
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
43
+ assert client.all_conn.length == @uris.length + 1
44
+ assert client.connected?.values.all?
45
+ client.disconnect
46
+ end
47
+
48
+ it "(#{adapter}) disconnnects" do
49
+ client = Klomp::Client.new(@uris.first, :adapter => adapter).connect
50
+ assert client.connected?.values.all?
51
+ client.disconnect
52
+ refute client.connected?.values.any?
53
+ end
54
+
55
+ it "(#{adapter}) sends requests and gets responses" do
56
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
57
+ body = { 'body' => rand(36**128).to_s(36) }
58
+
59
+ client.send(@destination, body, :ack=>'client')
60
+ got_message = false
61
+ client.subscribe(@destination) do |msg|
62
+ got_message = true if msg.body == body
63
+ client.ack(msg)
64
+ end
65
+ let_background_processor_run
66
+ client.disconnect
67
+
68
+ assert got_message
69
+ end
70
+
71
+ it "(#{adapter}) automatically publishes responses to the reply-to destination" do
72
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
73
+ reply_to_body = { 'reply_to_body' => rand(36**128).to_s(36) }
74
+
75
+ client.send(@destination, nil, { 'reply-to' => @destination })
76
+
77
+ got_message = false
78
+ client.subscribe(@destination) do |msg|
79
+ got_message = true if msg.body == reply_to_body
80
+ reply_to_body
81
+ end
82
+ let_background_processor_run
83
+ client.disconnect
84
+
85
+ assert got_message
86
+ end
87
+
88
+ it "(#{adapter}) sends messages with uuids in the 'id' header" do
89
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false).connect
90
+ client.send(@destination, '')
91
+
92
+ received_message = false
93
+ client.subscribe(@destination) do |msg|
94
+ received_message = msg
95
+ end
96
+ let_background_processor_run
97
+ client.disconnect
98
+
99
+ assert received_message
100
+ assert received_message.headers['id'], "message did not have an id"
101
+ assert received_message.headers['id'] =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
102
+ "message id did not look like a uuid"
103
+ end
104
+
105
+ it "(#{adapter}) allows customization of the uuid generator" do
106
+ generator = Object.new
107
+ def generator.generate; "42"; end
108
+
109
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :uuid => generator).connect
110
+ client.send(@destination, '')
111
+
112
+ received_message = false
113
+ client.subscribe(@destination) do |msg|
114
+ received_message = msg
115
+ end
116
+ let_background_processor_run
117
+ client.disconnect
118
+
119
+ assert received_message
120
+ assert received_message.headers['id'], "message did not have an id"
121
+ assert_equal "42", received_message.headers['id']
122
+ end
123
+
124
+ it "(#{adapter}) allows disabling generated message ids" do
125
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :uuid => false).connect
126
+ client.send(@destination, '')
127
+
128
+ received_message = false
129
+ client.subscribe(@destination) do |msg|
130
+ received_message = msg
131
+ end
132
+ let_background_processor_run
133
+ client.disconnect
134
+
135
+ assert received_message
136
+ refute received_message.headers['id'], "message had an id"
137
+ end
138
+
139
+ it "(#{adapter}) logs message ids" do
140
+ logger = Object.new
141
+ def logger.msgs; @msgs; end
142
+ def logger.debug(msg) (@msgs ||= []) << msg end
143
+
144
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :logger => logger).connect
145
+ client.send(@destination, '')
146
+
147
+ received_message = false
148
+ client.subscribe(@destination) do |msg|
149
+ received_message = msg
150
+ end
151
+ let_background_processor_run
152
+ client.disconnect
153
+
154
+ assert received_message
155
+ assert received_message.headers['id'], "message did not have an id"
156
+
157
+ assert_equal 2, logger.msgs.length
158
+ assert logger.msgs[0] =~ /\[Sending\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
159
+ sent_id = $1
160
+ assert logger.msgs[1] =~ /\[Received\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
161
+ received_id = $1
162
+ assert_equal sent_id, received_id
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -0,0 +1,81 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @adapter = :onstomp
13
+ @uris = [
14
+ 'stomp://admin:password@localhost:61613',
15
+ 'stomp://admin:password@127.0.0.1:62613'
16
+ ]
17
+ @destination = '/queue/test_component.test_event'
18
+ end
19
+
20
+ it "raises an error if authentication fails" do
21
+ assert_raises OnStomp::ConnectFailedError do
22
+ Klomp::Client.new(@uris.first.sub('password', 'psswrd'), :adapter => @adapter).connect
23
+ end
24
+ end
25
+
26
+ it "sends heartbeat" do
27
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
28
+ client.beat
29
+ client.disconnect
30
+ end
31
+
32
+ it "unsubscribes" do
33
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
34
+
35
+ subscribe_frames = client.subscribe(@destination) { |msg| }
36
+ unsub_frames = client.unsubscribe(subscribe_frames)
37
+ let_background_processor_run
38
+ client.disconnect
39
+
40
+ assert_equal subscribe_frames.length, unsub_frames.length
41
+ assert client.subscriptions.flatten.empty?, "expected connection to have no subscriptions"
42
+ end
43
+
44
+ it 'uses a fibonacci back-off approach to reconnect' do
45
+ good_client = Object.new
46
+ def good_client.connect; true; end
47
+ def good_client.connected?; true; end
48
+ def good_client.connection; true; end
49
+
50
+ bad_client = Object.new
51
+ def bad_client.connect; raise "could not connect"; end
52
+ def bad_client.connected?; false; end
53
+
54
+ test_context = self
55
+ attempts = 0
56
+ conn = nil
57
+ fib = lambda {|n| (1..n).inject([0, 1]) {|fib,_| [fib[1], fib[0]+fib[1]]}.first}
58
+
59
+ pool_class = Class.new do
60
+ def initialize(*) end
61
+ def each(&blk) end
62
+ define_method :next_client do
63
+ attempts += 1
64
+ test_context.assert_equal fib[attempts], conn.retry_delay
65
+ if attempts == 6
66
+ good_client
67
+ else
68
+ bad_client
69
+ end
70
+ end
71
+ end
72
+
73
+ client = Klomp::Client.new(@uris.first, :adapter => @adapter, :pool => pool_class)
74
+ conn = client.write_conn
75
+ def conn.sleep_for_retry(*) end # skip sleep between retries for test
76
+
77
+ client.reconnect
78
+ assert_equal 6, attempts
79
+ end
80
+
81
+ end
@@ -0,0 +1,27 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @adapter = :stomp
13
+ @uris = [
14
+ 'stomp://admin:password@localhost:61613',
15
+ 'stomp://admin:password@127.0.0.1:62613'
16
+ ]
17
+ @destination = '/queue/test_component.test_event'
18
+ end
19
+
20
+ it "unsubscribes" do
21
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
22
+ client.subscribe(@destination) { |msg| }
23
+ client.unsubscribe(@destination)
24
+ client.disconnect
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klomp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-22 00:00:00.000000000 Z
12
+ date: 2012-08-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: onstomp
@@ -78,7 +78,9 @@ files:
78
78
  - lib/klomp.rb
79
79
  - lib/klomp/client.rb
80
80
  - tasks/test_failover.rake
81
- - test/test_client.rb
81
+ - test/test_client_all.rb
82
+ - test/test_client_onstomp.rb
83
+ - test/test_client_stomp.rb
82
84
  - test/test_helper.rb
83
85
  homepage: https://github.com/livingsocial/klomp
84
86
  licenses: []
@@ -94,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
96
  version: '0'
95
97
  segments:
96
98
  - 0
97
- hash: 395786091883431087
99
+ hash: -1938950018895640841
98
100
  required_rubygems_version: !ruby/object:Gem::Requirement
99
101
  none: false
100
102
  requirements:
@@ -103,10 +105,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
105
  version: '0'
104
106
  requirements: []
105
107
  rubyforge_project:
106
- rubygems_version: 1.8.21
108
+ rubygems_version: 1.8.24
107
109
  signing_key:
108
110
  specification_version: 3
109
111
  summary: A simple wrapper around the OnStomp library with additional features
110
112
  test_files:
111
- - test/test_client.rb
113
+ - test/test_client_all.rb
114
+ - test/test_client_onstomp.rb
115
+ - test/test_client_stomp.rb
112
116
  - test/test_helper.rb
@@ -1,232 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'minitest/pride'
3
-
4
- require 'klomp'
5
- require File.expand_path('../test_helper', __FILE__)
6
-
7
- describe Klomp::Client do
8
-
9
- include KlompTestHelpers
10
-
11
- before do
12
- @uris = [
13
- 'stomp://admin:password@localhost:61613',
14
- 'stomp://admin:password@127.0.0.1:62613'
15
- ]
16
- @destination = '/queue/test_component.test_event'
17
- end
18
-
19
- it 'accepts a single uri and establishes separate failover connections for writes and reads' do
20
- client = Klomp::Client.new(@uris.first).connect
21
-
22
- assert_equal [client.write_conn], client.read_conn
23
- assert client.write_conn.connected?
24
-
25
- client.disconnect
26
- end
27
-
28
- it 'accepts an array of uris and establishes separate failover connections for writes and reads' do
29
- client = Klomp::Client.new(@uris).connect
30
-
31
- assert client.write_conn.connected?
32
- refute_empty client.read_conn
33
- client.read_conn.each do |obj|
34
- assert obj.connected?
35
- end
36
-
37
- client.disconnect
38
- end
39
-
40
- it 'raises an error if authentication fails' do
41
- assert_raises OnStomp::ConnectFailedError do
42
- Klomp::Client.new(@uris.first.sub('password', 'psswrd')).connect
43
- end
44
- end
45
-
46
- it 'disconnnects' do
47
- client = Klomp::Client.new(@uris.first).connect
48
- assert client.write_conn.connected?
49
- client.disconnect
50
- refute client.write_conn.connected?
51
- end
52
-
53
- it 'has a logger' do
54
- logger = Logger.new(STDOUT)
55
- client = Klomp::Client.new(@uris, :logger=>logger)
56
- assert_equal client.log, logger
57
- end
58
-
59
- it 'sends heartbeat' do
60
- client = Klomp::Client.new(@uris).connect
61
- client.beat
62
- client.disconnect
63
- end
64
-
65
- it 'sends requests and gets responses' do
66
- client = Klomp::Client.new(@uris).connect
67
- body = { 'body' => rand(36**128).to_s(36) }
68
-
69
- client.send(@destination, body, :ack=>'client')
70
-
71
- got_message = false
72
- client.subscribe(@destination) do |msg|
73
- got_message = true if msg.body == body
74
- client.ack(msg)
75
- end
76
- let_background_processor_run
77
- assert got_message
78
-
79
- client.disconnect
80
- end
81
-
82
- it 'automatically publishes responses to the reply-to destination' do
83
- client = Klomp::Client.new(@uris).connect
84
- reply_to_body = { 'reply_to_body' => rand(36**128).to_s(36) }
85
-
86
- client.send(@destination, nil, { 'reply-to' => @destination })
87
-
88
- got_message = false
89
- client.subscribe(@destination) do |msg|
90
- got_message = true if msg.body == reply_to_body
91
- reply_to_body
92
- end
93
- let_background_processor_run
94
- assert got_message
95
-
96
- client.disconnect
97
- end
98
-
99
- it 'unsubscribes' do
100
- client = Klomp::Client.new(@uris).connect
101
-
102
- subscribe_frames = client.subscribe(@destination) { |msg| }
103
- unsub_frames = client.unsubscribe(subscribe_frames)
104
- assert_equal subscribe_frames.length, unsub_frames.length
105
- let_background_processor_run
106
-
107
- assert client.subscriptions.flatten.empty?, "expected connection to have no subscriptions"
108
-
109
- client.disconnect
110
- end
111
-
112
- it 'sends all unknown options through to OnStomp' do
113
- client = Klomp::Client.new(@uris.first, :haz_cheezburgers => true, :retry_attempts => 42).connect
114
- assert client.write_conn.connected?
115
- assert_equal 42, client.write_conn.retry_attempts
116
- client.disconnect
117
- end
118
-
119
- it 'uses a fibonacci back-off approach to reconnect' do
120
- good_client = Object.new
121
- def good_client.connect; true; end
122
- def good_client.connected?; true; end
123
- def good_client.connection; true; end
124
-
125
- bad_client = Object.new
126
- def bad_client.connect; raise "could not connect"; end
127
- def bad_client.connected?; false; end
128
-
129
- test_context = self
130
- attempts = 0
131
- conn = nil
132
- fib = lambda {|n| (1..n).inject([0, 1]) {|fib,_| [fib[1], fib[0]+fib[1]]}.first}
133
-
134
- pool_class = Class.new do
135
- def initialize(*) end
136
- def each(&blk) end
137
- define_method :next_client do
138
- attempts += 1
139
- test_context.assert_equal fib[attempts], conn.retry_delay
140
- if attempts == 6
141
- good_client
142
- else
143
- bad_client
144
- end
145
- end
146
- end
147
-
148
- client = Klomp::Client.new(@uris.first, :pool => pool_class)
149
- conn = client.write_conn
150
- def conn.sleep_for_retry(*) end # skip sleep between retries for test
151
-
152
- client.reconnect
153
- assert_equal 6, attempts
154
- end
155
-
156
- it 'sends messages with uuids in the :id header' do
157
- client = Klomp::Client.new(@uris, :translate_json => false).connect
158
- client.send(@destination, '')
159
-
160
- received_message = false
161
- client.subscribe(@destination) do |msg|
162
- received_message = msg
163
- end
164
- let_background_processor_run
165
- assert received_message
166
- assert received_message[:id], "message did not have an id"
167
- assert received_message[:id] =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
168
- "message id did not look like a uuid"
169
-
170
- client.disconnect
171
- end
172
-
173
- it 'allows customization of the uuid generator' do
174
- generator = Object.new
175
- def generator.generate; "42"; end
176
-
177
- client = Klomp::Client.new(@uris, :translate_json => false, :uuid => generator).connect
178
- client.send(@destination, '')
179
-
180
- received_message = false
181
- client.subscribe(@destination) do |msg|
182
- received_message = msg
183
- end
184
- let_background_processor_run
185
- assert received_message
186
- assert received_message[:id], "message did not have an id"
187
- assert_equal "42", received_message[:id]
188
-
189
- client.disconnect
190
- end
191
-
192
- it 'allows disabling generated message ids' do
193
- client = Klomp::Client.new(@uris, :translate_json => false, :uuid => false).connect
194
- client.send(@destination, '')
195
-
196
- received_message = false
197
- client.subscribe(@destination) do |msg|
198
- received_message = msg
199
- end
200
- let_background_processor_run
201
- assert received_message
202
- refute received_message[:id], "message had an id"
203
-
204
- client.disconnect
205
- end
206
-
207
- it 'logs message ids' do
208
- logger = Object.new
209
- def logger.msgs; @msgs; end
210
- def logger.info(msg) (@msgs ||= []) << msg end
211
-
212
- client = Klomp::Client.new(@uris, :translate_json => false, :logger => logger).connect
213
- client.send(@destination, '')
214
-
215
- received_message = false
216
- client.subscribe(@destination) do |msg|
217
- received_message = msg
218
- end
219
- let_background_processor_run
220
- assert received_message
221
- assert received_message[:id], "message did not have an id"
222
-
223
- assert_equal 2, logger.msgs.length
224
- assert logger.msgs[0] =~ /\[Sending\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
225
- sent_id = $1
226
- assert logger.msgs[1] =~ /\[Received\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
227
- received_id = $1
228
- assert_equal sent_id, received_id
229
-
230
- client.disconnect
231
- end
232
- end