right_amqp 0.2.0

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