right_amqp 0.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,1385 @@
1
+ #
2
+ # Copyright (c) 2009-2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_amqp'))
25
+
26
+ class PushMock; end
27
+ class RequestMock; end
28
+
29
+ describe RightAMQP::HABrokerClient do
30
+
31
+ include FlexMock::ArgumentTypes
32
+ include RightAMQP::SpecHelper
33
+
34
+ before(:each) do
35
+ setup_logger
36
+ @exceptions = RightSupport::Stats::Exceptions
37
+ @message = "message"
38
+ @packet = flexmock("packet", :class => RequestMock, :to_s => true, :version => [12, 12]).by_default
39
+ @serializer = flexmock("serializer")
40
+ @serializer.should_receive(:dump).and_return(@message).by_default
41
+ @serializer.should_receive(:load).with(@message).and_return(@packet).by_default
42
+ end
43
+
44
+ describe "Context" do
45
+
46
+ before(:each) do
47
+ @packet1 = flexmock("packet1", :class => RequestMock, :name => "request", :type => "type1",
48
+ :from => "from1", :token => "token1", :one_way => false)
49
+ @packet2 = flexmock("packet2", :class => FlexMock, :name => "flexmock")
50
+ @brokers = ["broker"]
51
+ @options = {:option => "option"}
52
+ end
53
+
54
+ it "should initialize context" do
55
+ context = RightAMQP::HABrokerClient::Context.new(@packet1, @options, @brokers)
56
+ context.name.should == "request"
57
+ context.type.should == "type1"
58
+ context.from.should == "from1"
59
+ context.token.should == "token1"
60
+ context.one_way.should be_false
61
+ context.options.should == @options
62
+ context.brokers.should == @brokers
63
+ context.failed.should == []
64
+ end
65
+
66
+ it "should treat type, from, token, and one_way as optional members of packet but default one_way to true" do
67
+ context = RightAMQP::HABrokerClient::Context.new(@packet2, @options, @brokers)
68
+ context.name.should == "flexmock"
69
+ context.type.should be_nil
70
+ context.from.should be_nil
71
+ context.token.should be_nil
72
+ context.one_way.should be_true
73
+ context.options.should == @options
74
+ context.brokers.should == @brokers
75
+ context.failed.should == []
76
+ end
77
+
78
+ end
79
+
80
+ describe "Caching" do
81
+
82
+ require 'digest/md5'
83
+
84
+ before(:each) do
85
+ @now = Time.at(1000000)
86
+ @max_age = RightAMQP::HABrokerClient::Published::MAX_AGE
87
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
88
+ @published = RightAMQP::HABrokerClient::Published.new
89
+ @message1 = JSON.dump({:data => "a message"})
90
+ @key1 = Digest::MD5.hexdigest(@message1)
91
+ @message2 = JSON.dump({:data => "another message"})
92
+ @key2 = Digest::MD5.hexdigest(@message2)
93
+ @message3 = JSON.dump({:data => "yet another message"})
94
+ @key3 = Digest::MD5.hexdigest(@message3)
95
+ @packet1 = flexmock("packet1", :class => RequestMock, :name => "request", :type => "type1",
96
+ :from => "from1", :token => "token1", :one_way => false)
97
+ @packet2 = flexmock("packet2", :class => RequestMock, :name => "request", :type => "type2",
98
+ :from => "from2", :token => "token2", :one_way => false)
99
+ @packet3 = flexmock("packet3", :class => PushMock, :name => "push", :type => "type3",
100
+ :from => "from3", :token => "token3", :one_way => true)
101
+ @brokers = ["broker"]
102
+ @options = {:option => "option"}
103
+ @context1 = RightAMQP::HABrokerClient::Context.new(@packet1, @options, @brokers)
104
+ @context2 = RightAMQP::HABrokerClient::Context.new(@packet2, @options, @brokers)
105
+ @context3 = RightAMQP::HABrokerClient::Context.new(@packet3, @options, @brokers)
106
+ end
107
+
108
+ it "should use message signature as cache hash key if it has one" do
109
+ @published.identify(@message1).should == @key1
110
+ @published.identify(@message2).should == @key2
111
+ @published.identify(@message3).should == @key3
112
+ end
113
+
114
+ it "should store message info" do
115
+ @published.store(@message1, @context1)
116
+ @published.instance_variable_get(:@cache)[@key1].should == [@now.to_i, @context1]
117
+ @published.instance_variable_get(:@lru).should == [@key1]
118
+ end
119
+
120
+ it "should update timestamp and lru list when store to existing entry" do
121
+ @published.store(@message1, @context1)
122
+ @published.instance_variable_get(:@cache)[@key1].should == [@now.to_i, @context1]
123
+ @published.instance_variable_get(:@lru).should == [@key1]
124
+ @published.store(@message2, @context2)
125
+ @published.instance_variable_get(:@lru).should == [@key1, @key2]
126
+ flexmock(Time).should_receive(:now).and_return(Time.at(@now + @max_age - 1))
127
+ @published.store(@message1, @context1)
128
+ @published.instance_variable_get(:@cache)[@key1].should == [(@now + @max_age - 1).to_i, @context1]
129
+ @published.instance_variable_get(:@lru).should == [@key2, @key1]
130
+ end
131
+
132
+ it "should remove old cache entries when store new one" do
133
+ @published.store(@message1, @context1)
134
+ @published.store(@message2, @context2)
135
+ (@published.instance_variable_get(:@cache).keys - [@key1, @key2]).should == []
136
+ @published.instance_variable_get(:@lru).should == [@key1, @key2]
137
+ flexmock(Time).should_receive(:now).and_return(Time.at(@now + @max_age + 1))
138
+ @published.store(@message3, @context3)
139
+ @published.instance_variable_get(:@cache).keys.should == [@key3]
140
+ @published.instance_variable_get(:@lru).should == [@key3]
141
+ end
142
+
143
+ it "should fetch message info and make it the most recently used" do
144
+ @published.store(@message1, @context1)
145
+ @published.store(@message2, @context2)
146
+ @published.instance_variable_get(:@lru).should == [@key1, @key2]
147
+ @published.fetch(@message1).should == @context1
148
+ @published.instance_variable_get(:@lru).should == [@key2, @key1]
149
+ end
150
+
151
+ it "should fetch empty hash if entry not found" do
152
+ @published.fetch(@message1).should be_nil
153
+ @published.store(@message1, @context1)
154
+ @published.fetch(@message1).should_not be_nil
155
+ @published.fetch(@message2).should be_nil
156
+ end
157
+
158
+ end # Published
159
+
160
+ context "when initializing" do
161
+
162
+ before(:each) do
163
+ @identity = "rs-broker-localhost-5672"
164
+ @address = {:host => "localhost", :port => 5672, :index => 0}
165
+ @broker = flexmock("broker_client", :identity => @identity, :usable? => true)
166
+ @broker.should_receive(:return_message).by_default
167
+ @broker.should_receive(:update_status).by_default
168
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).and_return(@broker).by_default
169
+ end
170
+
171
+ it "should create a broker client for default host and port" do
172
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity, @address, @serializer,
173
+ @exceptions, Hash, nil).and_return(@broker).once
174
+ ha = RightAMQP::HABrokerClient.new(@serializer)
175
+ ha.brokers.should == [@broker]
176
+ end
177
+
178
+ it "should create broker clients for specified hosts and ports and assign index in order of creation" do
179
+ address1 = {:host => "first", :port => 5672, :index => 0}
180
+ broker1 = flexmock("broker_client1", :identity => "rs-broker-first-5672", :usable? => true, :return_message => true)
181
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with("rs-broker-first-5672", address1, @serializer,
182
+ @exceptions, Hash, nil).and_return(broker1).once
183
+ address2 = {:host => "second", :port => 5672, :index => 1}
184
+ broker2 = flexmock("broker_client2", :identity => "rs-broker-second-5672", :usable? => true, :return_message => true)
185
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with("rs-broker-second-5672", address2, @serializer,
186
+ @exceptions, Hash, nil).and_return(broker2).once
187
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second", :port => 5672)
188
+ ha.brokers.should == [broker1, broker2]
189
+ end
190
+
191
+ it "should setup to receive returned messages from each usable broker client" do
192
+ @broker.should_receive(:return_message).twice
193
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).and_return(@broker).twice
194
+ RightAMQP::HABrokerClient.new(@serializer, :host => "first, second", :port => 5672)
195
+ end
196
+
197
+ end # when initializing
198
+
199
+ context "when parsing user_data" do
200
+
201
+ it "should extra host list from RS_rn_url and RS_rn_host" do
202
+ RightAMQP::HABrokerClient.parse_user_data("RS_rn_url=rs@first/right_net&RS_rn_host=:0,second:1").should ==
203
+ ["first:0,second:1", nil]
204
+ end
205
+
206
+ it "should extra port list from RS_rn_port" do
207
+ RightAMQP::HABrokerClient.parse_user_data("RS_rn_url=rs@host/right_net&RS_rn_host=:1,host:0&RS_rn_port=5673:1,5672:0").should ==
208
+ ["host:1,host:0", "5673:1,5672:0"]
209
+ end
210
+
211
+ it "should raise an exception if there is no user data" do
212
+ lambda { RightAMQP::HABrokerClient.parse_user_data(nil) }.should raise_error(RightAMQP::HABrokerClient::NoUserData)
213
+ lambda { RightAMQP::HABrokerClient.parse_user_data("") }.should raise_error(RightAMQP::HABrokerClient::NoUserData)
214
+ end
215
+
216
+ it "should raise an exception if there are no broker hosts defined in the data" do
217
+ lambda { RightAMQP::HABrokerClient.parse_user_data("blah") }.should raise_error(RightAMQP::HABrokerClient::NoBrokerHosts)
218
+ end
219
+
220
+ it "should translate old host name to standard form" do
221
+ RightAMQP::HABrokerClient.parse_user_data("RS_rn_url=rs@broker.rightscale.com/right_net").should ==
222
+ ["broker1-1.rightscale.com", nil]
223
+ end
224
+
225
+ end # when parsing user_data
226
+
227
+ context "when addressing" do
228
+
229
+ it "should form list of broker addresses from specified hosts and ports" do
230
+ RightAMQP::HABrokerClient.addresses("first,second", "5672, 5674").should ==
231
+ [{:host => "first", :port => 5672, :index => 0}, {:host => "second", :port => 5674, :index => 1}]
232
+ end
233
+
234
+ it "should form list of broker addresses from specified hosts and ports and use ids associated with hosts" do
235
+ RightAMQP::HABrokerClient.addresses("first:1,second:2", "5672, 5674").should ==
236
+ [{:host => "first", :port => 5672, :index => 1}, {:host => "second", :port => 5674, :index => 2}]
237
+ end
238
+
239
+ it "should form list of broker addresses from specified hosts and ports and use ids associated with ports" do
240
+ RightAMQP::HABrokerClient.addresses("host", "5672:0, 5674:2").should ==
241
+ [{:host => "host", :port => 5672, :index => 0}, {:host => "host", :port => 5674, :index => 2}]
242
+ end
243
+
244
+ it "should use default host and port for broker identity if none provided" do
245
+ RightAMQP::HABrokerClient.addresses(nil, nil).should == [{:host => "localhost", :port => 5672, :index => 0}]
246
+ end
247
+
248
+ it "should use default port when ports is an empty string" do
249
+ RightAMQP::HABrokerClient.addresses("first, second", "").should ==
250
+ [{:host => "first", :port => 5672, :index => 0}, {:host => "second", :port => 5672, :index => 1}]
251
+ end
252
+
253
+ it "should use default host when hosts is an empty string" do
254
+ RightAMQP::HABrokerClient.addresses("", "5672, 5673").should ==
255
+ [{:host => "localhost", :port => 5672, :index => 0}, {:host => "localhost", :port => 5673, :index => 1}]
256
+ end
257
+
258
+ it "should reuse host if there is only one but multiple ports" do
259
+ RightAMQP::HABrokerClient.addresses("first", "5672, 5674").should ==
260
+ [{:host => "first", :port => 5672, :index => 0}, {:host => "first", :port => 5674, :index => 1}]
261
+ end
262
+
263
+ it "should reuse port if there is only one but multiple hosts" do
264
+ RightAMQP::HABrokerClient.addresses("first, second", 5672).should ==
265
+ [{:host => "first", :port => 5672, :index => 0}, {:host => "second", :port => 5672, :index => 1}]
266
+ end
267
+
268
+ it "should apply ids associated with host" do
269
+ RightAMQP::HABrokerClient.addresses("first:0, third:2", 5672).should ==
270
+ [{:host => "first", :port => 5672, :index => 0}, {:host => "third", :port => 5672, :index => 2}]
271
+ end
272
+
273
+ it "should not allow mismatched number of hosts and ports" do
274
+ runner = lambda { RightAMQP::HABrokerClient.addresses("first, second", "5672, 5673, 5674") }
275
+ runner.should raise_exception(ArgumentError)
276
+ end
277
+
278
+ end # when addressing
279
+
280
+ context "when identifying" do
281
+
282
+ before(:each) do
283
+ @address1 = {:host => "first", :port => 5672, :index => 0}
284
+ @identity1 = "rs-broker-first-5672"
285
+ @broker1 = flexmock("broker_client1", :identity => @identity1, :usable? => true, :return_message => true,
286
+ :alias => "b0", :host => "first", :port => 5672, :index => 0)
287
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity1, @address1, @serializer,
288
+ @exceptions, Hash, nil).and_return(@broker1).by_default
289
+
290
+ @address2 = {:host => "second", :port => 5672, :index => 1}
291
+ @identity2 = "rs-broker-second-5672"
292
+ @broker2 = flexmock("broker_client2", :identity => @identity2, :usable? => true, :return_message => true,
293
+ :alias => "b1", :host => "second", :port => 5672, :index => 1)
294
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity2, @address2, @serializer,
295
+ @exceptions, Hash, nil).and_return(@broker2).by_default
296
+
297
+ @address3 = {:host => "third", :port => 5672, :index => 2}
298
+ @identity3 = "rs-broker-third-5672"
299
+ @broker3 = flexmock("broker_client3", :identity => @identity3, :usable? => true, :return_message => true,
300
+ :alias => "b2", :host => "third", :port => 5672, :index => 2)
301
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity3, @address3, @serializer,
302
+ @exceptions, Hash, nil).and_return(@broker3).by_default
303
+ end
304
+
305
+ it "should use host and port to uniquely identity broker in AgentIdentity format" do
306
+ RightAMQP::HABrokerClient.identity("localhost", 5672).should == "rs-broker-localhost-5672"
307
+ RightAMQP::HABrokerClient.identity("10.21.102.23", 1234).should == "rs-broker-10.21.102.23-1234"
308
+ end
309
+
310
+ it "should replace '-' with '~' in host names when forming broker identity" do
311
+ RightAMQP::HABrokerClient.identity("9-1-1", 5672).should == "rs-broker-9~1~1-5672"
312
+ end
313
+
314
+ it "should use default port when forming broker identity" do
315
+ RightAMQP::HABrokerClient.identity("10.21.102.23").should == "rs-broker-10.21.102.23-5672"
316
+ end
317
+
318
+ it "should list broker identities" do
319
+ RightAMQP::HABrokerClient.identities("first,second", "5672, 5674").should ==
320
+ ["rs-broker-first-5672", "rs-broker-second-5674"]
321
+ end
322
+
323
+ it "should convert identities into aliases" do
324
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
325
+ ha.aliases([@identity3]).should == ["b2"]
326
+ ha.aliases([@identity3, @identity1]).should == ["b2", "b0"]
327
+ end
328
+
329
+ it "should convert identities into nil alias when unknown" do
330
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
331
+ ha.aliases(["rs-broker-second-5672", nil]).should == [nil, nil]
332
+ end
333
+
334
+ it "should convert identity into alias" do
335
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
336
+ ha.alias_(@identity3).should == "b2"
337
+ end
338
+
339
+ it "should convert identity into nil alias when unknown" do
340
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
341
+ ha.alias_("rs-broker-second-5672").should == nil
342
+ end
343
+
344
+ it "should convert identity into parts" do
345
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
346
+ ha.identity_parts(@identity3).should == ["third", 5672, 2, 1]
347
+ end
348
+
349
+ it "should convert an alias into parts" do
350
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
351
+ ha.identity_parts("b2").should == ["third", 5672, 2, 1]
352
+ end
353
+
354
+ it "should convert unknown identity into nil parts" do
355
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
356
+ ha.identity_parts("rs-broker-second-5672").should == [nil, nil, nil, nil]
357
+ end
358
+
359
+ it "should get identity from identity" do
360
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
361
+ ha.get(@identity1).should == @identity1
362
+ ha.get("rs-broker-second-5672").should be_nil
363
+ ha.get(@identity3).should == @identity3
364
+ end
365
+
366
+ it "should get identity from an alias" do
367
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first:0, third:2", :port => 5672)
368
+ ha.get("b0").should == @identity1
369
+ ha.get("b1").should be_nil
370
+ ha.get("b2").should == @identity3
371
+ end
372
+
373
+ it "should generate host:index list" do
374
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "second:1, first:0, third:2", :port => 5672)
375
+ ha.hosts.should == "second:1,first:0,third:2"
376
+ end
377
+
378
+ it "should generate port:index list" do
379
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "second:1, third:2, first:0", :port => 5672)
380
+ ha.ports.should == "5672:1,5672:2,5672:0"
381
+ end
382
+
383
+ end # when identifying
384
+
385
+ context "when" do
386
+
387
+ before(:each) do
388
+ # Generate mocking for three BrokerClients
389
+ # key index host alias
390
+ { 1 => [0, "first", "b0"],
391
+ 2 => [1, "second", "b1"],
392
+ 3 => [2, "third", "b2"] }.each do |k, v|
393
+ i, h, a = v
394
+ eval("@identity#{k} = 'rs-broker-#{h}-5672'")
395
+ eval("@address#{k} = {:host => '#{h}', :port => 5672, :index => #{i}}")
396
+ eval("@broker#{k} = flexmock('broker_client#{k}', :identity => @identity#{k}, :alias => '#{a}', " +
397
+ ":host => '#{h}', :port => 5672, :index => #{i})")
398
+ eval("@broker#{k}.should_receive(:status).and_return(:connected).by_default")
399
+ eval("@broker#{k}.should_receive(:usable?).and_return(true).by_default")
400
+ eval("@broker#{k}.should_receive(:connected?).and_return(true).by_default")
401
+ eval("@broker#{k}.should_receive(:subscribe).and_return(true).by_default")
402
+ eval("@broker#{k}.should_receive(:return_message).and_return(true).by_default")
403
+ eval("flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity#{k}, @address#{k}, " +
404
+ "@serializer, @exceptions, Hash, nil).and_return(@broker#{k}).by_default")
405
+ end
406
+ end
407
+
408
+ context "connecting" do
409
+
410
+ it "should connect and add a new broker client to the end of the list" do
411
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first", :port => 5672)
412
+ ha.brokers.size.should == 1
413
+ ha.brokers[0].alias == "b0"
414
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity2, @address2, @serializer,
415
+ @exceptions, Hash, nil).and_return(@broker2).once
416
+ res = ha.connect("second", 5672, 1)
417
+ res.should be_true
418
+ ha.brokers.size.should == 2
419
+ ha.brokers[1].alias == "b1"
420
+ end
421
+
422
+ it "should reconnect an existing broker client after closing it if it is not connected" do
423
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
424
+ ha.brokers.size.should == 2
425
+ @broker1.should_receive(:usable?).and_return(false)
426
+ @broker1.should_receive(:close).and_return(true).once
427
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity1, @address1, @serializer,
428
+ @exceptions, Hash, ha.brokers[0]).and_return(@broker1).once
429
+ res = ha.connect("first", 5672, 0)
430
+ res.should be_true
431
+ ha.brokers.size.should == 2
432
+ ha.brokers[0].alias == "b0"
433
+ ha.brokers[1].alias == "b1"
434
+ end
435
+
436
+ it "should not do anything except log a message if asked to reconnect an already connected broker client" do
437
+ @logger.should_receive(:info).with(/Ignored request to reconnect/).once
438
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
439
+ ha.brokers.size.should == 2
440
+ @broker1.should_receive(:status).and_return(:connected).once
441
+ @broker1.should_receive(:close).and_return(true).never
442
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity1, @address1, @serializer,
443
+ @exceptions, Hash, ha.brokers[0]).and_return(@broker1).never
444
+ res = ha.connect("first", 5672, 0)
445
+ res.should be_false
446
+ ha.brokers.size.should == 2
447
+ ha.brokers[0].alias == "b0"
448
+ ha.brokers[1].alias == "b1"
449
+ end
450
+
451
+ it "should reconnect already connected broker client if force specified" do
452
+ @logger.should_receive(:info).with(/Ignored request to reconnect/).never
453
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
454
+ ha.brokers.size.should == 2
455
+ @broker1.should_receive(:close).and_return(true).once
456
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity1, @address1, @serializer,
457
+ @exceptions, Hash, ha.brokers[0]).and_return(@broker1).once
458
+ res = ha.connect("first", 5672, 0, nil, force = true)
459
+ res.should be_true
460
+ ha.brokers.size.should == 2
461
+ ha.brokers[0].alias == "b0"
462
+ ha.brokers[1].alias == "b1"
463
+ end
464
+
465
+ it "should be able to change host and port of an existing broker client" do
466
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
467
+ ha.brokers.size.should == 2
468
+ @broker1.should_receive(:close).and_return(true).once
469
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity3, @address3.merge(:index => 0),
470
+ @serializer, @exceptions, Hash, nil).and_return(@broker3).once
471
+ res = ha.connect("third", 5672, 0)
472
+ res.should be_true
473
+ ha.brokers.size.should == 2
474
+ ha.brokers[0].alias == "b0"
475
+ ha.brokers[0].identity == @address3
476
+ ha.brokers[1].alias == "b1"
477
+ ha.brokers[1].identity == @address_b
478
+ end
479
+
480
+ it "should slot broker client into specified priority position when at end of list" do
481
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
482
+ ha.brokers.size.should == 2
483
+ res = ha.connect("third", 5672, 2, 2)
484
+ res.should be_true
485
+ ha.brokers.size.should == 3
486
+ ha.brokers[0].alias == "b0"
487
+ ha.brokers[1].alias == "b1"
488
+ ha.brokers[2].alias == "b2"
489
+ end
490
+
491
+ it "should slot broker client into specified priority position when already is a client in that position" do
492
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
493
+ ha.brokers.size.should == 2
494
+ res = ha.connect("third", 5672, 2, 1)
495
+ res.should be_true
496
+ ha.brokers.size.should == 3
497
+ ha.brokers[0].alias == "b0"
498
+ ha.brokers[1].alias == "b2"
499
+ ha.brokers[2].alias == "b1"
500
+ end
501
+
502
+ it "should slot broker client into nex priority position if specified priority would leave a gap" do
503
+ @logger.should_receive(:info).with(/Reduced priority setting for broker/).once
504
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first")
505
+ ha.brokers.size.should == 1
506
+ res = ha.connect("third", 5672, 2, 2)
507
+ res.should be_true
508
+ ha.brokers.size.should == 2
509
+ ha.brokers[0].alias == "b0"
510
+ ha.brokers[1].alias == "b2"
511
+ end
512
+
513
+ it "should yield to the block provided with the newly connected broker identity" do
514
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first")
515
+ ha.brokers.size.should == 1
516
+ ha.brokers[0].alias == "b0"
517
+ identity = nil
518
+ res = ha.connect("second", 5672, 1) { |i| identity = i }
519
+ res.should be_true
520
+ identity.should == @identity2
521
+ ha.brokers.size.should == 2
522
+ ha.brokers[1].alias == "b1"
523
+ end
524
+
525
+ end # connecting
526
+
527
+ context "subscribing" do
528
+
529
+ it "should subscribe on all usable broker clients and return their identities" do
530
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
531
+ @broker1.should_receive(:usable?).and_return(false)
532
+ @broker1.should_receive(:subscribe).never
533
+ @broker2.should_receive(:subscribe).and_return(true).once
534
+ @broker3.should_receive(:subscribe).and_return(true).once
535
+ result = ha.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"})
536
+ result.should == [@identity2, @identity3]
537
+ end
538
+
539
+ it "should not return the identity if subscribe fails" do
540
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
541
+ @broker1.should_receive(:usable?).and_return(false)
542
+ @broker1.should_receive(:subscribe).never
543
+ @broker2.should_receive(:subscribe).and_return(true).once
544
+ @broker3.should_receive(:subscribe).and_return(false).once
545
+ result = ha.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"})
546
+ result.should == [@identity2]
547
+ end
548
+
549
+ it "should subscribe only on specified brokers" do
550
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
551
+ @broker1.should_receive(:usable?).and_return(false)
552
+ @broker1.should_receive(:subscribe).never
553
+ @broker2.should_receive(:subscribe).and_return(true).once
554
+ @broker3.should_receive(:subscribe).never
555
+ result = ha.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
556
+ :brokers => [@identity1, @identity2])
557
+ result.should == [@identity2]
558
+ end
559
+
560
+ end # subscribing
561
+
562
+ context "unsubscribing" do
563
+
564
+ before(:each) do
565
+ @timer = flexmock("timer", :cancel => true).by_default
566
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
567
+ @queue_name = "my_queue"
568
+ @queue = flexmock("queue", :name => @queue_name)
569
+ @queues = [@queue]
570
+ @broker1.should_receive(:queues).and_return(@queues).by_default
571
+ @broker1.should_receive(:unsubscribe).and_return(true).and_yield.by_default
572
+ @broker2.should_receive(:queues).and_return(@queues).by_default
573
+ @broker2.should_receive(:unsubscribe).and_return(true).and_yield.by_default
574
+ @broker3.should_receive(:queues).and_return(@queues).by_default
575
+ @broker3.should_receive(:unsubscribe).and_return(true).and_yield.by_default
576
+ end
577
+
578
+ it "should unsubscribe from named queues on all usable broker clients" do
579
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
580
+ @broker1.should_receive(:usable?).and_return(false)
581
+ @broker1.should_receive(:unsubscribe).never
582
+ @broker2.should_receive(:unsubscribe).and_return(true).once
583
+ @broker3.should_receive(:unsubscribe).and_return(true).once
584
+ ha.unsubscribe([@queue_name]).should be_true
585
+ end
586
+
587
+ it "should yield to supplied block after unsubscribing" do
588
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
589
+ ha.subscribe({:name => @queue_name}, {:type => :direct, :name => "exchange"})
590
+ called = 0
591
+ ha.unsubscribe([@queue_name]) { called += 1 }
592
+ called.should == 1
593
+ end
594
+
595
+ it "should yield to supplied block if timeout before finish unsubscribing" do
596
+ flexmock(EM::Timer).should_receive(:new).with(10, Proc).and_return(@timer).and_yield.once
597
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
598
+ ha.subscribe({:name => @queue_name}, {:type => :direct, :name => "exchange"})
599
+ called = 0
600
+ ha.unsubscribe([@queue_name], 10) { called += 1 }
601
+ called.should == 1
602
+ end
603
+
604
+ it "should cancel timer if finish unsubscribing before timer fires" do
605
+ @timer.should_receive(:cancel).once
606
+ flexmock(EM::Timer).should_receive(:new).with(10, Proc).and_return(@timer).once
607
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
608
+ ha.subscribe({:name => @queue_name}, {:type => :direct, :name => "exchange"})
609
+ called = 0
610
+ ha.unsubscribe([@queue_name], 10) { called += 1 }
611
+ called.should == 1
612
+ end
613
+
614
+ it "should yield to supplied block after unsubscribing even if no queues to unsubscribe" do
615
+ @broker1.should_receive(:queues).and_return([])
616
+ @broker2.should_receive(:queues).and_return([])
617
+ @broker3.should_receive(:queues).and_return([])
618
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
619
+ called = 0
620
+ ha.unsubscribe([@queue_name]) { called += 1 }
621
+ called.should == 1
622
+ end
623
+
624
+ it "should yield to supplied block once after unsubscribing all queues" do
625
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
626
+ ha.subscribe({:name => @queue_name}, {:type => :direct, :name => "exchange"})
627
+ called = 0
628
+ ha.unsubscribe([@queue_name]) { called += 1 }
629
+ called.should == 1
630
+ end
631
+
632
+ end # unsubscribing
633
+
634
+ context "declaring" do
635
+
636
+ it "should declare exchange on all usable broker clients and return their identities" do
637
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
638
+ @broker1.should_receive(:usable?).and_return(false)
639
+ @broker1.should_receive(:declare).never
640
+ @broker2.should_receive(:declare).and_return(true).once
641
+ @broker3.should_receive(:declare).and_return(true).once
642
+ result = ha.declare(:exchange, "x", :durable => true)
643
+ result.should == [@identity2, @identity3]
644
+ end
645
+
646
+ it "should not return the identity if declare fails" do
647
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
648
+ @broker1.should_receive(:usable?).and_return(false)
649
+ @broker1.should_receive(:declare).never
650
+ @broker2.should_receive(:declare).and_return(true).once
651
+ @broker3.should_receive(:declare).and_return(false).once
652
+ result = ha.declare(:exchange, "x", :durable => true)
653
+ result.should == [@identity2]
654
+ end
655
+
656
+ it "should declare exchange only on specified brokers" do
657
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
658
+ @broker1.should_receive(:usable?).and_return(false)
659
+ @broker1.should_receive(:declare).never
660
+ @broker2.should_receive(:declare).and_return(true).once
661
+ @broker3.should_receive(:declare).never
662
+ result = ha.declare(:exchange, "x", :durable => true, :brokers => [@identity1, @identity2])
663
+ result.should == [@identity2]
664
+ end
665
+
666
+ end # declaring
667
+
668
+ context "publishing" do
669
+
670
+ before(:each) do
671
+ @broker1.should_receive(:publish).and_return(true).by_default
672
+ @broker2.should_receive(:publish).and_return(true).by_default
673
+ @broker3.should_receive(:publish).and_return(true).by_default
674
+ end
675
+
676
+ it "should serialize message, publish it, and return list of broker identifiers" do
677
+ @serializer.should_receive(:dump).with(@packet).and_return(@message).once
678
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
679
+ ha.publish({:type => :direct, :name => "exchange", :options => {:durable => true}},
680
+ @packet, :persistent => true).should == [@identity1]
681
+ end
682
+
683
+ it "should try other broker clients if a publish fails" do
684
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
685
+ @broker1.should_receive(:publish).and_return(false)
686
+ ha.publish({:type => :direct, :name => "exchange"}, @packet).should == [@identity2]
687
+ end
688
+
689
+ it "should publish to a randomly selected broker if random requested" do
690
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
691
+ srand(100)
692
+ ha.publish({:type => :direct, :name => "exchange"}, @packet, :order => :random,
693
+ :brokers =>[@identity1, @identity2, @identity3]).should == [@identity2]
694
+ end
695
+
696
+ it "should publish to all connected brokers if fanout requested" do
697
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
698
+ ha.publish({:type => :direct, :name => "exchange"}, @packet, :fanout => true,
699
+ :brokers =>[@identity1, @identity2]).should == [@identity1, @identity2]
700
+ end
701
+
702
+ it "should publish only using specified brokers" do
703
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
704
+ ha.publish({:type => :direct, :name => "exchange"}, @packet,
705
+ :brokers =>[@identity1, @identity2]).should == [@identity1]
706
+ end
707
+
708
+ it "should log an error if a selected broker is unknown but still publish with any remaining brokers" do
709
+ @logger.should_receive(:error).with(/Invalid broker identity "rs-broker-fourth-5672"/).once
710
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
711
+ ha.publish({:type => :direct, :name => "exchange"}, @packet,
712
+ :brokers =>["rs-broker-fourth-5672", @identity1]).should == [@identity1]
713
+ end
714
+
715
+ it "should raise an exception if all available brokers fail to publish" do
716
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
717
+ @broker1.should_receive(:publish).and_return(false)
718
+ @broker2.should_receive(:publish).and_return(false)
719
+ @broker3.should_receive(:publish).and_return(false)
720
+ lambda { ha.publish({:type => :direct, :name => "exchange"}, @packet) }.
721
+ should raise_error(RightAMQP::HABrokerClient::NoConnectedBrokers)
722
+ end
723
+
724
+ it "should not serialize the message if it is already serialized" do
725
+ @serializer.should_receive(:dump).with(@packet).and_return(@message).never
726
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
727
+ ha.publish({:type => :direct, :name => "exchange"}, @packet, :no_serialize => true).should == [@identity1]
728
+ end
729
+
730
+ it "should store message info for use by message returns if :mandatory specified" do
731
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
732
+ ha.publish({:type => :direct, :name => "exchange"}, @packet, :mandatory => true).should == [@identity1]
733
+ ha.instance_variable_get(:@published).instance_variable_get(:@cache).size.should == 1
734
+ end
735
+
736
+ it "should not store message info for use by message returns if message already serialized" do
737
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
738
+ ha.publish({:type => :direct, :name => "exchange"}, @packet, :no_serialize => true).should == [@identity1]
739
+ ha.instance_variable_get(:@published).instance_variable_get(:@cache).size.should == 0
740
+ end
741
+
742
+ it "should not store message info for use by message returns if mandatory not specified" do
743
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
744
+ ha.publish({:type => :direct, :name => "exchange"}, @packet).should == [@identity1]
745
+ ha.instance_variable_get(:@published).instance_variable_get(:@cache).size.should == 0
746
+ end
747
+
748
+ end # publishing
749
+
750
+ context "returning" do
751
+
752
+ before(:each) do
753
+ @broker1.should_receive(:publish).and_return(true).by_default
754
+ @broker2.should_receive(:publish).and_return(true).by_default
755
+ @broker3.should_receive(:publish).and_return(true).by_default
756
+ end
757
+
758
+ it "should invoke return block" do
759
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
760
+ @broker1.should_receive(:return_message).and_yield("exchange", "NO_CONSUMERS", @message).once
761
+ called = 0
762
+ ha.return_message do |id, reason, message, to, context|
763
+ called += 1
764
+ id.should == @identity1
765
+ reason.should == "NO_CONSUMERS"
766
+ message.should == @message
767
+ to.should == "exchange"
768
+ end
769
+ called.should == 1
770
+ end
771
+
772
+ it "should record failure in message context if there is message context" do
773
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
774
+ ha.publish({:type => :direct, :name => "exchange", :options => {:durable => true}},
775
+ @packet, :mandatory => true).should == [@identity1]
776
+ @broker1.should_receive(:return_message).and_yield("exchange", "NO_CONSUMERS", @message).once
777
+ ha.return_message do |id, reason, message, to, context|
778
+ id.should == @identity1
779
+ reason.should == "NO_CONSUMERS"
780
+ message.should == @message
781
+ to.should == "exchange"
782
+ end
783
+ ha.instance_variable_get(:@published).fetch(@message).failed.should == [@identity1]
784
+ end
785
+
786
+ context "when non-delivery" do
787
+
788
+ it "should store non-delivery block for use by return handler" do
789
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
790
+ non_delivery = lambda {}
791
+ ha.non_delivery(&non_delivery)
792
+ ha.instance_variable_get(:@non_delivery).should == non_delivery
793
+ end
794
+
795
+ end
796
+
797
+ context "when handling return" do
798
+
799
+ before(:each) do
800
+ @options = {}
801
+ @brokers = [@identity1, @identity2]
802
+ @context = RightAMQP::HABrokerClient::Context.new(@packet, @options, @brokers)
803
+ end
804
+
805
+ it "should republish using a broker not yet tried if possible and log that re-routing" do
806
+ @logger.should_receive(:info).with(/RE-ROUTE/).once
807
+ @logger.should_receive(:info).with(/RETURN reason/).once
808
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
809
+ @context.record_failure(@identity1)
810
+ @broker2.should_receive(:publish).and_return(true).once
811
+ ha.__send__(:handle_return, @identity1, "reason", @message, "to", @context)
812
+ end
813
+
814
+ it "should republish to same broker without mandatory if message is persistent and no other brokers available" do
815
+ @logger.should_receive(:info).with(/RE-ROUTE/).once
816
+ @logger.should_receive(:info).with(/RETURN reason/).once
817
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
818
+ @context.record_failure(@identity1)
819
+ @context.record_failure(@identity2)
820
+ @packet.should_receive(:persistent).and_return(true)
821
+ @broker1.should_receive(:publish).and_return(true).once
822
+ ha.__send__(:handle_return, @identity2, "NO_CONSUMERS", @message, "to", @context)
823
+ end
824
+
825
+ it "should republish to same broker without mandatory if message is one-way and no other brokers available" do
826
+ @logger.should_receive(:info).with(/RE-ROUTE/).once
827
+ @logger.should_receive(:info).with(/RETURN reason/).once
828
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
829
+ @context.record_failure(@identity1)
830
+ @context.record_failure(@identity2)
831
+ @packet.should_receive(:one_way).and_return(true)
832
+ @broker1.should_receive(:publish).and_return(true).once
833
+ ha.__send__(:handle_return, @identity2, "NO_CONSUMERS", @message, "to", @context)
834
+ end
835
+
836
+ it "should update status to :stopping if message returned because access refused" do
837
+ @logger.should_receive(:info).with(/RE-ROUTE/).once
838
+ @logger.should_receive(:info).with(/RETURN reason/).once
839
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
840
+ @context.record_failure(@identity1)
841
+ @broker2.should_receive(:publish).and_return(true).once
842
+ @broker1.should_receive(:update_status).with(:stopping).and_return(true).once
843
+ ha.__send__(:handle_return, @identity1, "ACCESS_REFUSED", @message, "to", @context)
844
+ end
845
+
846
+ it "should log info and make non-delivery call even if persistent when returned because of no queue" do
847
+ @logger.should_receive(:info).with(/NO ROUTE/).once
848
+ @logger.should_receive(:info).with(/RETURN reason/).once
849
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
850
+ called = 0
851
+ ha.non_delivery { |reason, type, token, from, to| called += 1 }
852
+ @context.record_failure(@identity1)
853
+ @context.record_failure(@identity2)
854
+ @packet.should_receive(:persistent).and_return(true)
855
+ @broker1.should_receive(:publish).and_return(true).never
856
+ @broker2.should_receive(:publish).and_return(true).never
857
+ ha.__send__(:handle_return, @identity2, "NO_QUEUE", @message, "to", @context)
858
+ called.should == 1
859
+ end
860
+
861
+ it "should log info and make non-delivery call if no route can be found" do
862
+ @logger.should_receive(:info).with(/NO ROUTE/).once
863
+ @logger.should_receive(:info).with(/RETURN reason/).once
864
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
865
+ called = 0
866
+ ha.non_delivery { |reason, type, token, from, to| called += 1 }
867
+ @context.record_failure(@identity1)
868
+ @context.record_failure(@identity2)
869
+ @broker1.should_receive(:publish).and_return(true).never
870
+ @broker2.should_receive(:publish).and_return(true).never
871
+ ha.__send__(:handle_return, @identity2, "any reason", @message, "to", @context)
872
+ called.should == 1
873
+ end
874
+
875
+ it "should log info if no message context available for re-routing it" do
876
+ @logger.should_receive(:info).with(/Dropping/).once
877
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
878
+ ha.__send__(:handle_return, @identity2, "any reason", @message, "to", nil)
879
+ end
880
+
881
+ end
882
+
883
+ end # returning
884
+
885
+ context "deleting" do
886
+
887
+ it "should delete queue on all usable broker clients and return their identities" do
888
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
889
+ @broker1.should_receive(:usable?).and_return(false)
890
+ @broker1.should_receive(:delete).never
891
+ @broker2.should_receive(:delete).and_return(true).once
892
+ @broker3.should_receive(:delete).and_return(true).once
893
+ ha.delete("queue").should == [@identity2, @identity3]
894
+ end
895
+
896
+ it "should not return the identity if delete fails" do
897
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
898
+ @broker1.should_receive(:usable?).and_return(false)
899
+ @broker1.should_receive(:delete).never
900
+ @broker2.should_receive(:delete).and_return(true).once
901
+ @broker3.should_receive(:delete).and_return(false).once
902
+ ha.delete("queue").should == [@identity2]
903
+ end
904
+
905
+ it "should delete queue from cache on all usable broker clients and return their identities" do
906
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
907
+ @broker1.should_receive(:usable?).and_return(false)
908
+ @broker1.should_receive(:delete_amqp_resources).never
909
+ @broker2.should_receive(:delete_amqp_resources).and_return(true).once
910
+ @broker3.should_receive(:delete_amqp_resources).and_return(true).once
911
+ ha.delete_amqp_resources("queue").should == [@identity2, @identity3]
912
+ end
913
+
914
+ end # deleting
915
+
916
+ context "removing" do
917
+
918
+ it "should remove broker client after disconnecting and pass identity to block" do
919
+ @logger.should_receive(:info).with(/Removing/).once
920
+ @broker2.should_receive(:close).with(true, true, false).once
921
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
922
+ identity = nil
923
+ result = ha.remove("second", 5672) { |i| identity = i }
924
+ result.should == @identity2
925
+ identity.should == @identity2
926
+ ha.get(@identity1).should_not be_nil
927
+ ha.get(@identity2).should be_nil
928
+ ha.get(@identity3).should_not be_nil
929
+ ha.brokers.size.should == 2
930
+ end
931
+
932
+ it "should remove broker when no block supplied but still return a result" do
933
+ @logger.should_receive(:info).with(/Removing/).once
934
+ @broker2.should_receive(:close).once
935
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
936
+ result = ha.remove("second", 5672)
937
+ result.should == @identity2
938
+ ha.get(@identity1).should_not be_nil
939
+ ha.get(@identity2).should be_nil
940
+ ha.get(@identity3).should_not be_nil
941
+ ha.brokers.size.should == 2
942
+ end
943
+
944
+ it "should remove last broker if requested" do
945
+ @logger.should_receive(:info).with(/Removing/).times(3)
946
+ @broker1.should_receive(:close).once
947
+ @broker2.should_receive(:close).once
948
+ @broker3.should_receive(:close).once
949
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
950
+ result = ha.remove("second", 5672)
951
+ result.should == @identity2
952
+ ha.get(@identity2).should be_nil
953
+ result = ha.remove("third", 5672)
954
+ result.should == @identity3
955
+ ha.get(@identity3).should be_nil
956
+ ha.brokers.size.should == 1
957
+ identity = nil
958
+ result = ha.remove("first", 5672) { |i| identity = i }
959
+ result.should == @identity1
960
+ identity.should == @identity1
961
+ ha.get(@identity1).should be_nil
962
+ ha.brokers.size.should == 0
963
+ end
964
+
965
+ it "should return nil and not execute block if broker is unknown" do
966
+ @logger.should_receive(:info).with(/Ignored request to remove/).once
967
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
968
+ ha.remove("fourth", 5672).should be_nil
969
+ ha.brokers.size.should == 3
970
+ end
971
+
972
+ it "should close connection and mark as failed when told broker is not usable" do
973
+ @broker2.should_receive(:close).with(true, false, false).once
974
+ @broker3.should_receive(:close).with(true, false, false).once
975
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
976
+ result = ha.declare_unusable([@identity2, @identity3])
977
+ ha.brokers.size.should == 3
978
+ end
979
+
980
+ it "should raise an exception if broker that is declared not usable is unknown" do
981
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
982
+ lambda { ha.declare_unusable(["rs-broker-fourth-5672"]) }.should raise_error(Exception, /Cannot mark unknown/)
983
+ ha.brokers.size.should == 3
984
+ end
985
+
986
+ end # removing
987
+
988
+ context "monitoring" do
989
+
990
+ before(:each) do
991
+ @timer = flexmock("timer")
992
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
993
+ @timer.should_receive(:cancel).by_default
994
+ @identity = "rs-broker-localhost-5672"
995
+ @address = {:host => "localhost", :port => 5672, :index => 0}
996
+ @broker = flexmock("broker_client", :identity => @identity, :alias => "b0", :host => "localhost",
997
+ :port => 5672, :index => 0)
998
+ @broker.should_receive(:status).and_return(:connected).by_default
999
+ @broker.should_receive(:usable?).and_return(true).by_default
1000
+ @broker.should_receive(:connected?).and_return(true).by_default
1001
+ @broker.should_receive(:subscribe).and_return(true).by_default
1002
+ @broker.should_receive(:return_message).and_return(true).by_default
1003
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).and_return(@broker).by_default
1004
+ @broker1.should_receive(:failed?).and_return(false).by_default
1005
+ @broker2.should_receive(:failed?).and_return(false).by_default
1006
+ @broker3.should_receive(:failed?).and_return(false).by_default
1007
+ end
1008
+
1009
+ it "should give access to or list usable brokers" do
1010
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1011
+ aliases = []
1012
+ res = ha.__send__(:each_usable) { |b| aliases << b.alias }
1013
+ aliases.should == ["b0", "b1", "b2"]
1014
+ res.size.should == 3
1015
+ res[0].alias.should == "b0"
1016
+ res[1].alias.should == "b1"
1017
+ res[2].alias.should == "b2"
1018
+
1019
+ @broker1.should_receive(:usable?).and_return(true)
1020
+ @broker2.should_receive(:usable?).and_return(false)
1021
+ @broker3.should_receive(:usable?).and_return(false)
1022
+ aliases = []
1023
+ res = ha.__send__(:each_usable) { |b| aliases << b.alias }
1024
+ aliases.should == ["b0"]
1025
+ res.size.should == 1
1026
+ res[0].alias.should == "b0"
1027
+ end
1028
+
1029
+ it "should give list of unusable brokers" do
1030
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1031
+ @broker1.should_receive(:usable?).and_return(true)
1032
+ @broker2.should_receive(:usable?).and_return(false)
1033
+ @broker3.should_receive(:usable?).and_return(false)
1034
+ ha.unusable.should == [@identity2, @identity3]
1035
+ end
1036
+
1037
+ it "should give access to each selected usable broker" do
1038
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1039
+ @broker2.should_receive(:usable?).and_return(true)
1040
+ @broker3.should_receive(:usable?).and_return(false)
1041
+ aliases = []
1042
+ res = ha.__send__(:each_usable, [@identity2, @identity3]) { |b| aliases << b.alias }
1043
+ aliases.should == ["b1"]
1044
+ res.size.should == 1
1045
+ res[0].alias.should == "b1"
1046
+ end
1047
+
1048
+ it "should tell whether a broker is connected" do
1049
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1050
+ @broker2.should_receive(:connected?).and_return(false)
1051
+ @broker3.should_receive(:connected?).and_return(true)
1052
+ ha.connected?(@identity2).should be_false
1053
+ ha.connected?(@identity3).should be_true
1054
+ ha.connected?("rs-broker-fourth-5672").should be_nil
1055
+ end
1056
+
1057
+ it "should give list of all brokers" do
1058
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1059
+ ha.all.should == [@identity1, @identity2, @identity3]
1060
+ end
1061
+
1062
+ it "should give list of failed brokers" do
1063
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1064
+ @broker1.should_receive(:failed?).and_return(true)
1065
+ @broker2.should_receive(:failed?).and_return(false)
1066
+ @broker3.should_receive(:failed?).and_return(true)
1067
+ ha.failed.should == [@identity1, @identity3]
1068
+ end
1069
+
1070
+ it "should give broker client status list" do
1071
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1072
+ @broker1.should_receive(:summary).and_return("summary1")
1073
+ @broker2.should_receive(:summary).and_return("summary2")
1074
+ @broker3.should_receive(:summary).and_return("summary3")
1075
+ ha.status.should == ["summary1", "summary2", "summary3"]
1076
+ end
1077
+
1078
+ it "should give broker client statistics" do
1079
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1080
+ @broker1.should_receive(:stats).and_return("stats1")
1081
+ @broker2.should_receive(:stats).and_return("stats2")
1082
+ @broker3.should_receive(:stats).and_return("stats3")
1083
+ ha.stats.should == {"brokers" => ["stats1", "stats2", "stats3"],
1084
+ "exceptions" => nil,
1085
+ "heartbeat" => nil,
1086
+ "returns" => nil}
1087
+ end
1088
+
1089
+ it "should log broker client status update if there is a change" do
1090
+ @logger.should_receive(:info).with(/Broker b0 is now connected/).once
1091
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1092
+ ha.__send__(:update_status, @broker1, false)
1093
+ end
1094
+
1095
+ it "should not log broker client status update if there is no change" do
1096
+ @logger.should_receive(:info).with(/Broker b0 is now connected/).never
1097
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1098
+ ha.__send__(:update_status, @broker1, true)
1099
+ end
1100
+
1101
+ it "should log broker client status update when become disconnected" do
1102
+ @logger.should_receive(:info).with(/Broker b0 is now disconnected/).once
1103
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1104
+ @broker1.should_receive(:status).and_return(:disconnected)
1105
+ @broker1.should_receive(:connected?).and_return(false)
1106
+ ha.__send__(:update_status, @broker1, true)
1107
+ end
1108
+
1109
+ it "should provide connection status callback when cross 0/1 connection boundary" do
1110
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
1111
+ connected = 0
1112
+ disconnected = 0
1113
+ ha.connection_status do |status|
1114
+ if status == :connected
1115
+ (ha.brokers[0].status == :connected ||
1116
+ ha.brokers[1].status == :connected).should be_true
1117
+ connected += 1
1118
+ elsif status == :disconnected
1119
+ (ha.brokers[0].status == :disconnected &&
1120
+ ha.brokers[1].status == :disconnected).should be_true
1121
+ disconnected += 1
1122
+ end
1123
+ end
1124
+ ha.__send__(:update_status, @broker1, false)
1125
+ connected.should == 0
1126
+ disconnected.should == 0
1127
+ @broker1.should_receive(:status).and_return(:disconnected)
1128
+ @broker1.should_receive(:connected?).and_return(false)
1129
+ ha.__send__(:update_status, @broker1, true)
1130
+ connected.should == 0
1131
+ disconnected.should == 0
1132
+ @broker2.should_receive(:status).and_return(:disconnected)
1133
+ @broker2.should_receive(:connected?).and_return(false)
1134
+ ha.__send__(:update_status, @broker2, true)
1135
+ connected.should == 0
1136
+ disconnected.should == 1
1137
+ # TODO fix this test so that also checks crossing boundary as become connected
1138
+ end
1139
+
1140
+ it "should provide connection status callback when cross n/n-1 connection boundary when all specified" do
1141
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second")
1142
+ connected = 0
1143
+ disconnected = 0
1144
+ ha.connection_status(:boundary => :all) do |status|
1145
+ if status == :connected
1146
+ (ha.brokers[0].status == :connected &&
1147
+ ha.brokers[1].status == :connected).should be_true
1148
+ connected += 1
1149
+ elsif status == :disconnected
1150
+ (ha.brokers[0].status == :disconnected ||
1151
+ ha.brokers[1].status == :disconnected).should be_true
1152
+ disconnected += 1
1153
+ end
1154
+ end
1155
+ ha.__send__(:update_status, @broker1, false)
1156
+ connected.should == 1
1157
+ disconnected.should == 0
1158
+ @broker1.should_receive(:status).and_return(:disconnected)
1159
+ @broker1.should_receive(:connected?).and_return(false)
1160
+ ha.__send__(:update_status, @broker1, true)
1161
+ connected.should == 1
1162
+ disconnected.should == 1
1163
+ @broker2.should_receive(:status).and_return(:disconnected)
1164
+ @broker2.should_receive(:connected?).and_return(false)
1165
+ ha.__send__(:update_status, @broker2, true)
1166
+ connected.should == 1
1167
+ disconnected.should == 1
1168
+ # TODO fix this test so that also checks crossing boundary as become disconnected
1169
+ end
1170
+
1171
+ it "should provide connection status callback for specific broker set" do
1172
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1173
+ connected = 0
1174
+ disconnected = 0
1175
+ ha.connection_status(:brokers => [@identity2, @identity3]) do |status|
1176
+ if status == :connected
1177
+ (ha.brokers[1].status == :connected ||
1178
+ ha.brokers[2].status == :connected).should be_true
1179
+ connected += 1
1180
+ elsif status == :disconnected
1181
+ (ha.brokers[1].status == :disconnected &&
1182
+ ha.brokers[2].status == :disconnected).should be_true
1183
+ disconnected += 1
1184
+ end
1185
+ end
1186
+ ha.__send__(:update_status, @broker1, false)
1187
+ connected.should == 0
1188
+ disconnected.should == 0
1189
+ @broker1.should_receive(:status).and_return(:disconnected)
1190
+ @broker1.should_receive(:connected?).and_return(false)
1191
+ ha.__send__(:update_status, @broker1, true)
1192
+ connected.should == 0
1193
+ disconnected.should == 0
1194
+ @broker2.should_receive(:status).and_return(:disconnected)
1195
+ @broker2.should_receive(:connected?).and_return(false)
1196
+ ha.__send__(:update_status, @broker2, true)
1197
+ connected.should == 0
1198
+ disconnected.should == 0
1199
+ @broker3.should_receive(:status).and_return(:disconnected)
1200
+ @broker3.should_receive(:connected?).and_return(false)
1201
+ ha.__send__(:update_status, @broker3, true)
1202
+ connected.should == 0
1203
+ disconnected.should == 1
1204
+ end
1205
+
1206
+ it "should provide connection status callback only once when one-off is requested" do
1207
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity, @address, @serializer,
1208
+ @exceptions, Hash, nil).and_return(@broker).once
1209
+ ha = RightAMQP::HABrokerClient.new(@serializer)
1210
+ called = 0
1211
+ ha.connection_status(:one_off => 10) { |_| called += 1 }
1212
+ ha.__send__(:update_status, @broker, false)
1213
+ called.should == 1
1214
+ @broker.should_receive(:status).and_return(:disconnected)
1215
+ @broker.should_receive(:connected?).and_return(false)
1216
+ ha.__send__(:update_status, @broker, true)
1217
+ called.should == 1
1218
+ end
1219
+
1220
+ it "should use connection status timer when one-off is requested" do
1221
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
1222
+ @timer.should_receive(:cancel).once
1223
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity, @address, @serializer,
1224
+ @exceptions, Hash, nil).and_return(@broker).once
1225
+ ha = RightAMQP::HABrokerClient.new(@serializer)
1226
+ called = 0
1227
+ ha.connection_status(:one_off => 10) { |_| called += 1 }
1228
+ ha.__send__(:update_status, @broker, false)
1229
+ called.should == 1
1230
+ end
1231
+
1232
+ it "should give timeout connection status if one-off request times out" do
1233
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).and_yield.once
1234
+ @timer.should_receive(:cancel).never
1235
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity, @address, @serializer,
1236
+ @exceptions, Hash, nil).and_return(@broker).once
1237
+ ha = RightAMQP::HABrokerClient.new(@serializer)
1238
+ called = 0
1239
+ ha.connection_status(:one_off => 10) { |status| called += 1; status.should == :timeout }
1240
+ called.should == 1
1241
+ end
1242
+
1243
+ it "should be able to have multiple connection status callbacks" do
1244
+ flexmock(RightAMQP::BrokerClient).should_receive(:new).with(@identity, @address, @serializer,
1245
+ @exceptions, Hash, nil).and_return(@broker).once
1246
+ ha = RightAMQP::HABrokerClient.new(@serializer)
1247
+ called1 = 0
1248
+ called2 = 0
1249
+ ha.connection_status(:one_off => 10) { |_| called1 += 1 }
1250
+ ha.connection_status(:boundary => :all) { |_| called2 += 1 }
1251
+ ha.__send__(:update_status, @broker, false)
1252
+ @broker.should_receive(:status).and_return(:disconnected)
1253
+ @broker.should_receive(:connected?).and_return(false)
1254
+ ha.__send__(:update_status, @broker, true)
1255
+ called1.should == 1
1256
+ called2.should == 2
1257
+ end
1258
+
1259
+ it "should provide failed connection status callback when all brokers fail to connect" do
1260
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1261
+ connected = disconnected = failed = 0
1262
+ ha.connection_status(:boundary => :all) do |status|
1263
+ if status == :connected
1264
+ connected += 1
1265
+ elsif status == :disconnected
1266
+ disconnected += 1
1267
+ elsif status == :failed
1268
+ (ha.brokers[0].failed? &&
1269
+ ha.brokers[1].failed? &&
1270
+ ha.brokers[2].failed?).should be_true
1271
+ failed += 1
1272
+ end
1273
+ end
1274
+ @broker1.should_receive(:failed?).and_return(true)
1275
+ @broker1.should_receive(:connected?).and_return(false)
1276
+ ha.__send__(:update_status, @broker1, false)
1277
+ connected.should == 0
1278
+ disconnected.should == 0
1279
+ failed.should == 0
1280
+ @broker2.should_receive(:failed?).and_return(true)
1281
+ @broker2.should_receive(:connected?).and_return(false)
1282
+ ha.__send__(:update_status, @broker2, false)
1283
+ connected.should == 0
1284
+ disconnected.should == 0
1285
+ failed.should == 0
1286
+ @broker3.should_receive(:failed?).and_return(true)
1287
+ @broker3.should_receive(:connected?).and_return(false)
1288
+ ha.__send__(:update_status, @broker3, false)
1289
+ connected.should == 0
1290
+ disconnected.should == 0
1291
+ failed.should == 1
1292
+ end
1293
+
1294
+ it "should provide failed connection status callback when brokers selected and all brokers fail to connect" do
1295
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1296
+ connected = disconnected = failed = 0
1297
+ ha.connection_status(:boundary => :all, :brokers => [@broker2.identity, @broker3.identity]) do |status|
1298
+ if status == :connected
1299
+ connected += 1
1300
+ elsif status == :disconnected
1301
+ disconnected += 1
1302
+ elsif status == :failed
1303
+ (ha.brokers[0].failed? &&
1304
+ ha.brokers[1].failed?).should be_true
1305
+ failed += 1
1306
+ end
1307
+ end
1308
+ @broker1.should_receive(:failed?).and_return(true)
1309
+ @broker2.should_receive(:failed?).and_return(true)
1310
+ @broker2.should_receive(:connected?).and_return(false)
1311
+ ha.__send__(:update_status, @broker2, false)
1312
+ connected.should == 0
1313
+ disconnected.should == 0
1314
+ failed.should == 0
1315
+ @broker3.should_receive(:failed?).and_return(true)
1316
+ @broker3.should_receive(:connected?).and_return(false)
1317
+ ha.__send__(:update_status, @broker3, false)
1318
+ connected.should == 0
1319
+ disconnected.should == 0
1320
+ failed.should == 1
1321
+ end
1322
+
1323
+ end # monitoring
1324
+
1325
+ context "closing" do
1326
+
1327
+ it "should close all broker connections and execute block after all connections are closed" do
1328
+ @broker1.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1329
+ @broker2.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1330
+ @broker3.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1331
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1332
+ called = 0
1333
+ ha.close { called += 1 }
1334
+ called.should == 1
1335
+ end
1336
+
1337
+ it "should close broker connections when no block supplied" do
1338
+ @broker1.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1339
+ @broker2.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1340
+ @broker3.should_receive(:close).with(false, Proc).and_return(true).and_yield.once
1341
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1342
+ ha.close
1343
+ end
1344
+
1345
+ it "should close all broker connections even if encounter an exception" do
1346
+ @logger.should_receive(:error).with(/Failed to close/).once
1347
+ @broker1.should_receive(:close).and_return(true).and_yield.once
1348
+ @broker2.should_receive(:close).and_raise(Exception).once
1349
+ @broker3.should_receive(:close).and_return(true).and_yield.once
1350
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1351
+ called = 0
1352
+ ha.close { called += 1 }
1353
+ called.should == 1
1354
+ end
1355
+
1356
+ it "should close an individual broker connection" do
1357
+ @broker1.should_receive(:close).with(true).and_return(true).once
1358
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1359
+ ha.close_one(@identity1)
1360
+ end
1361
+
1362
+ it "should not propagate connection status change if requested not to" do
1363
+ @broker1.should_receive(:close).with(false).and_return(true).once
1364
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1365
+ ha.close_one(@identity1, propagate = false)
1366
+ end
1367
+
1368
+ it "should close an individual broker connection and execute block if given" do
1369
+ @broker1.should_receive(:close).with(true, Proc).and_return(true).and_yield.once
1370
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1371
+ called = 0
1372
+ ha.close_one(@identity1) { called += 1 }
1373
+ called.should == 1
1374
+ end
1375
+
1376
+ it "should raise exception if unknown broker" do
1377
+ ha = RightAMQP::HABrokerClient.new(@serializer, :host => "first, second, third")
1378
+ lambda { ha.close_one("rs-broker-fourth-5672") }.should raise_error(Exception, /Cannot close unknown broker/)
1379
+ end
1380
+
1381
+ end # closing
1382
+
1383
+ end # when
1384
+
1385
+ end # RightAMQP::HABrokerClient