right_agent 2.0.8 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,8 +32,8 @@ describe RightScale::RouterClient do
32
32
  @log = flexmock(RightScale::Log)
33
33
  @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
34
34
  @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
35
- @timer = flexmock("timer", :cancel => true, :interval= => 0).by_default
36
- flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).by_default
35
+ @timer = flexmock("timer", :cancel => true).by_default
36
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
37
37
  @http_client = flexmock("http client", :get => true, :check_health => true).by_default
38
38
  flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).by_default
39
39
  @websocket = WebSocketClientMock.new
@@ -94,13 +94,13 @@ describe RightScale::RouterClient do
94
94
  it "makes post request to router" do
95
95
  flexmock(@client).should_receive(:make_request).with(:post, "/push", @params, @action, @token).
96
96
  and_return(nil).once
97
- @client.push(@type, @payload, @target, @token).should be_nil
97
+ @client.push(@type, @payload, @target, @token).should be nil
98
98
  end
99
99
 
100
100
  it "does not require token" do
101
101
  flexmock(@client).should_receive(:make_request).with(:post, "/push", @params, @action, nil).
102
102
  and_return(nil).once
103
- @client.push(@type, @payload, @target).should be_nil
103
+ @client.push(@type, @payload, @target).should be nil
104
104
  end
105
105
  end
106
106
 
@@ -114,7 +114,7 @@ describe RightScale::RouterClient do
114
114
  it "does not require token" do
115
115
  flexmock(@client).should_receive(:make_request).with(:post, "/request", @params, @action, nil).
116
116
  and_return(nil).once
117
- @client.request(@type, @payload, @target).should be_nil
117
+ @client.request(@type, @payload, @target).should be nil
118
118
  end
119
119
  end
120
120
  end
@@ -122,8 +122,9 @@ describe RightScale::RouterClient do
122
122
  context "events" do
123
123
 
124
124
  before(:each) do
125
+ @handler = lambda { |_| }
125
126
  @later = Time.at(@now = Time.now)
126
- @tick = 30
127
+ @tick = 1
127
128
  flexmock(Time).should_receive(:now).and_return { @later += @tick }
128
129
  end
129
130
 
@@ -137,14 +138,14 @@ describe RightScale::RouterClient do
137
138
 
138
139
  it "sends using websocket if available" do
139
140
  @client.send(:connect, @routing_keys) { |_| }
140
- @client.notify(@event, @routing_keys).should be_true
141
+ @client.notify(@event, @routing_keys).should be true
141
142
  @websocket.sent.should == JSON.dump(@params)
142
143
  end
143
144
 
144
145
  it "makes post request by default" do
145
146
  flexmock(@client).should_receive(:make_request).with(:post, "/notify", @params, "notify", "uuid",
146
147
  {:filter_params => ["event"]}).once
147
- @client.notify(@event, @routing_keys).should be_true
148
+ @client.notify(@event, @routing_keys).should be true
148
149
  end
149
150
  end
150
151
 
@@ -153,164 +154,323 @@ describe RightScale::RouterClient do
153
154
  lambda { @client.listen(@routing_keys) }.should raise_error(ArgumentError, "Block missing")
154
155
  end
155
156
 
156
- it "loops forever until closed" do
157
+ it "initializes listen state and starts loop" do
158
+ flexmock(@client).should_receive(:listen_loop).with(@routing_keys, Proc).and_return(true).once
159
+ @client.listen(@routing_keys, &@handler).should be true
160
+ @client.instance_variable_get(:@listen_state).should == :choose
161
+ end
162
+ end
163
+
164
+ context :close do
165
+ it "stops listening" do
166
+ @client.instance_variable_set(:@listen_timer, @timer)
167
+ @timer.should_receive(:cancel).once
157
168
  @client.close
158
- @client.listen(@routing_keys) { |_| }.should be_true
169
+ @client.instance_variable_get(:@listen_timer).should be nil
170
+ @client.instance_variable_get(:@listen_state).should == :cancel
159
171
  end
160
172
 
161
- it "loops forever until closing" do
162
- @client.close(:receive)
163
- @client.listen(@routing_keys) { |_| }.should be_true
173
+ it "closes websocket" do
174
+ @client.send(:connect, nil, &@handler)
175
+ @client.close
176
+ @websocket.closed.should be true
177
+ @websocket.code.should == 1001
164
178
  end
179
+ end
165
180
 
166
- it "sleeps if websocket already exists" do
167
- @client.send(:connect, @routing_keys) { |_| }
168
- flexmock(@client).should_receive(:sleep).with(5).and_return { @client.close }.once
169
- @client.listen(@routing_keys) { |_| }
170
- @client.instance_variable_get(:@connect_interval).should == 30
181
+ context :update_listen_state do
182
+ it "cancels timer if state is :cancel" do
183
+ @client.instance_variable_set(:@listen_timer, @timer)
184
+ @client.send(:update_listen_state, :cancel).should be true
185
+ @client.instance_variable_get(:@listen_state).should == :cancel
186
+ @client.instance_variable_get(:@listen_timer).should be nil
171
187
  end
172
188
 
173
- it "tries to create websocket if time to" do
174
- @client.instance_variable_get(:@websocket).should be_nil
175
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
176
- flexmock(@client).should_receive(:sleep).and_return { @client.close }
177
- @client.listen(@routing_keys) { |_| }
189
+ it "can handle a re-cancel" do
190
+ @client.instance_variable_set(:@listen_timer, @timer)
191
+ @client.send(:update_listen_state, :cancel).should be true
192
+ @client.send(:update_listen_state, :cancel).should be true
193
+ @client.instance_variable_get(:@listen_timer).should be nil
178
194
  end
179
195
 
180
- it "does not try long-polling if websocket connect attempt indicates not to" do
181
- @client.instance_variable_get(:@websocket).should be_nil
182
- flexmock(@client).should_receive(:try_connect).and_return { @client.close; true }
183
- flexmock(@client).should_receive(:try_long_poll).never
184
- @client.listen(@routing_keys) { |_| }
196
+ [:choose, :check, :connect, :long_poll, :wait].each do |state|
197
+ it "sets state and timer interval for valid state #{state}" do
198
+ @client.send(:update_listen_state, state, 10).should be true
199
+ @client.instance_variable_get(:@listen_state).should == state
200
+ @client.instance_variable_get(:@listen_interval).should == 10
201
+ end
185
202
  end
186
203
 
187
- it "tries long-polling if could not create websocket" do
188
- @client.instance_variable_get(:@websocket).should be_nil
189
- flexmock(@client).should_receive(:retry_connect?).and_return(false)
190
- flexmock(@client).should_receive(:long_poll).and_return { @client.close; ["uuid"] }.once
191
- @client.listen(@routing_keys) { |_| }.should be_true
204
+ it "rejects invalid states" do
205
+ lambda { @client.send(:update_listen_state, :bogus) }.should raise_error(ArgumentError)
192
206
  end
193
- end
194
207
 
195
- context :close do
196
- it "closes websocket" do
197
- @client.send(:connect, nil) { |_| }
198
- @client.close
199
- @websocket.closed.should be_true
200
- @websocket.code.should == 1001
208
+ context "and state set to :check" do
209
+ it "initializes check count" do
210
+ @client.instance_variable_get(:@listen_checks).should be nil
211
+ @client.send(:update_listen_state, :check).should be true
212
+ @client.instance_variable_get(:@listen_checks).should == 0
213
+ end
214
+
215
+ it "only records start of long-polling when state changes" do
216
+ @client.send(:update_listen_state, :check).should be true
217
+ @client.instance_variable_get(:@listen_checks).should == 0
218
+ @client.instance_variable_set(:@listen_checks, nil)
219
+ @client.send(:update_listen_state, :check)
220
+ @client.instance_variable_get(:@listen_checks).should be nil
221
+ end
201
222
  end
202
223
  end
203
224
 
204
- context :retry_connect? do
205
- before(:each) do
206
- @client.instance_variable_set(:@last_connect_time, @now)
225
+ context :listen_loop do
226
+ def when_in_listen_state(state, checks = nil, failures = 0)
227
+ flexmock(EM).should_receive(:next_tick).by_default
228
+ flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
229
+ @client.send(:update_listen_state, state)
230
+ @client.instance_variable_set(:@listen_checks, checks) if checks
231
+ @client.instance_variable_set(:@listen_failures, failures)
207
232
  @client.instance_variable_set(:@connect_interval, 30)
233
+ @client.instance_variable_set(:@reconnect_interval, 2)
234
+ state
208
235
  end
209
236
 
210
- it "requires websocket to be enabled" do
211
- @client = RightScale::RouterClient.new(@auth_client, :long_polling_only => true)
212
- @client.send(:retry_connect?).should be_false
237
+ context "in :choose state" do
238
+ it "chooses listen method" do
239
+ when_in_listen_state(:choose)
240
+ @client.send(:listen_loop, @routing_keys, &@handler)
241
+ @client.instance_variable_get(:@listen_state).should == :connect
242
+ end
213
243
  end
214
244
 
215
- it "requires there be no existing websocket connection" do
216
- @client.instance_variable_set(:@websocket, @websocket)
217
- @client.send(:retry_connect?).should be_false
245
+ context "in :check state" do
246
+ context "and not connected" do
247
+ before(:each) do
248
+ when_in_listen_state(:check)
249
+ @client.instance_variable_set(:@websocket, nil)
250
+ end
251
+
252
+ it "sets state to :connect if router not responding" do
253
+ flexmock(@client).should_receive(:router_not_responding?).and_return(true).once
254
+ @client.send(:listen_loop, @routing_keys, &@handler)
255
+ @client.instance_variable_get(:@listen_state).should == :connect
256
+ @client.instance_variable_get(:@listen_interval).should == 4
257
+ end
258
+
259
+ it "otherwise backs off connect interval and sets state to :long_poll" do
260
+ flexmock(@client).should_receive(:router_not_responding?).and_return(false).once
261
+ @client.send(:listen_loop, @routing_keys, &@handler)
262
+ @client.instance_variable_get(:@connect_interval).should == 60
263
+ @client.instance_variable_get(:@listen_state).should == :long_poll
264
+ @client.instance_variable_get(:@listen_interval).should == 0
265
+ end
266
+ end
267
+
268
+ context "and connected" do
269
+ before(:each) do
270
+ @client.instance_variable_set(:@websocket, @websocket)
271
+ end
272
+
273
+ it "sets state to :choose if have checked enough" do
274
+ when_in_listen_state(:check, 5)
275
+ @client.send(:listen_loop, @routing_keys, &@handler)
276
+ @client.instance_variable_get(:@listen_state).should == :choose
277
+ @client.instance_variable_get(:@listen_interval).should == 30
278
+ end
279
+
280
+ it "otherwise stays in same state" do
281
+ when_in_listen_state(:check, 4)
282
+ @client.send(:listen_loop, @routing_keys, &@handler)
283
+ @client.instance_variable_get(:@listen_state).should == :check
284
+ end
285
+ end
286
+ end
287
+
288
+ context "in :connect state" do
289
+ it "tries to connect" do
290
+ when_in_listen_state(:connect)
291
+ flexmock(@client).should_receive(:try_connect).with(@routing_keys, Proc).once
292
+ @client.send(:listen_loop, @routing_keys, &@handler)
293
+ end
294
+ end
295
+
296
+ context "in :long_poll state" do
297
+ it "tries long-polling if non-blocking enabled" do
298
+ @client = RightScale::RouterClient.new(@auth_client, :non_blocking => true)
299
+ when_in_listen_state(:long_poll)
300
+ event_uuids = ["uuids"]
301
+ flexmock(@client).should_receive(:try_long_poll).with(@routing_keys, nil, Proc).and_return(event_uuids).once
302
+ @client.send(:listen_loop, @routing_keys, &@handler)
303
+ @client.instance_variable_get(:@event_uuids).should == event_uuids
304
+ end
305
+
306
+ it "otherwise tries deferred long-polling and sets state to :wait" do
307
+ when_in_listen_state(:long_poll)
308
+ event_uuids = ["uuids"]
309
+ @client.instance_variable_set(:@event_uuids, event_uuids)
310
+ flexmock(@client).should_receive(:try_deferred_long_poll).with(@routing_keys, event_uuids, Proc).once
311
+ @client.send(:listen_loop, @routing_keys, &@handler)
312
+ @client.instance_variable_get(:@listen_state).should == :wait
313
+ @client.instance_variable_get(:@listen_interval).should == 1
314
+ end
315
+ end
316
+
317
+ context "in :wait state" do
318
+ it "does nothing" do
319
+ when_in_listen_state(:wait)
320
+ @client.send(:listen_loop, @routing_keys, &@handler)
321
+ @client.instance_variable_get(:@listen_state).should == :wait
322
+ end
323
+ end
324
+
325
+ context "in :cancel state" do
326
+ it "returns false" do
327
+ when_in_listen_state(:cancel)
328
+ @client.send(:listen_loop, @routing_keys, &@handler).should be false
329
+ @client.instance_variable_get(:@listen_state).should == :cancel
330
+ end
218
331
  end
219
332
 
220
- context "when no existing websocket" do
333
+ context "when unexpected exception" do
221
334
  before(:each) do
222
- @client.instance_variable_get(:@websocket).should be_nil
335
+ when_in_listen_state(:connect)
336
+ flexmock(@client).should_receive(:try_connect).and_raise(RuntimeError).once
223
337
  end
224
338
 
225
- it "allows retry if enough time has elapsed" do
226
- @tick = 1
227
- @client.instance_variable_set(:@last_connect_time, @now - 29)
228
- @client.send(:retry_connect?).should be_false
229
- @client.instance_variable_set(:@last_connect_time, @now - 30)
230
- @client.send(:retry_connect?).should be_true
339
+ it "logs error" do
340
+ @log.should_receive(:error).with("Failed to listen", RuntimeError, :trace).once
341
+ @client.send(:listen_loop, @routing_keys, &@handler).should be true
231
342
  end
232
343
 
233
- [RightScale::RouterClient::NORMAL_CLOSE, RightScale::RouterClient::SHUTDOWN_CLOSE].each do |code|
234
- it "allows retry if previous close code is #{code}" do
235
- @client.instance_variable_set(:@close_code, code)
236
- @client.instance_variable_set(:@connect_interval, 300)
237
- @client.send(:retry_connect?).should be_true
238
- end
344
+ it "resets state to :choose" do
345
+ @log.should_receive(:error)
346
+ @client.send(:listen_loop, @routing_keys, &@handler).should be true
347
+ @client.instance_variable_get(:@listen_state).should == :choose
239
348
  end
240
349
 
241
- [502, 503].each do |code|
242
- it "allows retry if previous close has reason with code #{code} indicating router inaccessible" do
243
- @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
244
- @client.instance_variable_set(:@close_reason, "Unexpected response code: #{code}")
245
- @client.instance_variable_set(:@connect_interval, 300)
246
- @client.send(:retry_connect?).should be_true
247
- end
350
+ it "fails if exceeded repeated failure limit" do
351
+ when_in_listen_state(:connect, 1, 10)
352
+ @log.should_receive(:error).with("Failed to listen", RuntimeError, :trace).once.ordered
353
+ @log.should_receive(:error).with("Exceeded maximum repeated listen failures (10), stopping listening").once.ordered
354
+ @client.send(:listen_loop, @routing_keys, &@handler).should be false
355
+ @client.instance_variable_get(:@listen_state).should == :cancel
356
+ @client.state.should == :failed
248
357
  end
249
358
  end
359
+
360
+ it "uses next_tick for next loop if interval is 0" do
361
+ when_in_listen_state(:choose)
362
+ @client.instance_variable_get(:@listen_interval).should == 0
363
+ flexmock(EM).should_receive(:next_tick).and_yield.once
364
+ flexmock(EM::Timer).should_receive(:new).with(1, Proc).and_return(@timer).once
365
+ @client.send(:listen_loop, @routing_keys, &@handler).should be true
366
+ @client.instance_variable_get(:@listen_state).should == :check
367
+ end
368
+
369
+ it "otherwise uses timer for next loop" do
370
+ when_in_listen_state(:long_poll)
371
+ flexmock(@client).should_receive(:try_deferred_long_poll).once
372
+ flexmock(EM::Timer).should_receive(:new).with(1, Proc).and_return(@timer).once
373
+ flexmock(EM).should_receive(:next_tick).never
374
+ @client.send(:listen_loop, @routing_keys, &@handler).should be true
375
+ @client.instance_variable_get(:@listen_state).should == :wait
376
+ end
250
377
  end
251
378
 
252
- context :try_connect do
379
+ context :choose_listen_method do
253
380
  before(:each) do
254
- @handler = lambda { |_| }
255
- @client.instance_variable_get(:@websocket).should be_nil
256
381
  @client.instance_variable_set(:@connect_interval, 30)
257
- @client.instance_variable_set(:@reconnect_interval, 2)
258
382
  end
259
383
 
260
- it "makes websocket connect request" do
261
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
262
- flexmock(@client).should_receive(:sleep).and_return { @client.close }
263
- @client.send(:try_connect, @routing_keys, &@handler)
384
+ it "chooses long-polling if only it is enabled" do
385
+ @client = RightScale::RouterClient.new(@auth_client, :long_polling_only => true)
386
+ @client.send(:choose_listen_method).should be true
387
+ @client.instance_variable_get(:@listen_state).should == :long_poll
388
+ @client.instance_variable_get(:@listen_interval).should == 0
389
+ @client.instance_variable_get(:@connect_interval).should == 60 * 60 * 24
264
390
  end
265
391
 
266
- it "periodically checks whether websocket creation was really successful" do
267
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
268
- flexmock(@client).should_receive(:sleep).with(1).times(3).ordered
269
- flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil) }.once.ordered
270
- @client.send(:try_connect, @routing_keys, &@handler)
392
+ it "chooses to delay choice if already connected" do
393
+ @client.instance_variable_set(:@websocket, @websocket)
394
+ @client.send(:choose_listen_method).should be true
395
+ @client.instance_variable_get(:@listen_state).should == :choose
396
+ @client.instance_variable_get(:@listen_interval).should == 30
271
397
  end
272
398
 
273
- it "sleeps if websocket creation failed because router not responding" do
274
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
275
- flexmock(@client).should_receive(:sleep).with(1).and_return do
276
- @client.instance_variable_set(:@websocket, nil)
277
- @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
278
- @client.instance_variable_set(:@close_reason, "Unexpected response code: 502")
279
- end.once
280
- flexmock(@client).should_receive(:sleep).with(4).and_return { @client.close }.once
281
- @client.send(:try_connect, @routing_keys, &@handler)
282
- @client.instance_variable_get(:@reconnect_interval).should == 4
283
- @client.instance_variable_get(:@connect_interval).should == 30
284
- end
399
+ context "when not connected" do
400
+ before(:each) do
401
+ @client.instance_variable_get(:@websocket).should be nil
402
+ end
285
403
 
286
- it "adjusts connect interval if websocket creation was unsuccessful" do
287
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
288
- flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil); @client.close }.once
289
- @client.send(:try_connect, @routing_keys, &@handler)
290
- @client.instance_variable_get(:@connect_interval).should == 60
291
- @client.instance_variable_get(:@reconnect_interval).should == 2
404
+ it "chooses to connect if never connected" do
405
+ @client.send(:choose_listen_method).should be true
406
+ @client.instance_variable_get(:@listen_state).should == :connect
407
+ @client.instance_variable_get(:@listen_interval).should == 0
408
+ end
409
+
410
+ context "but previously attempted" do
411
+ before(:each) do
412
+ @client.instance_variable_set(:@attempted_connect_at, @now)
413
+ end
414
+
415
+ it "chooses to connect immediately if enough time has elapsed" do
416
+ @client.instance_variable_set(:@attempted_connect_at, @now - 30)
417
+ @client.send(:choose_listen_method).should be true
418
+ @client.instance_variable_get(:@listen_state).should == :connect
419
+ @client.instance_variable_get(:@listen_interval).should == 0
420
+ end
421
+
422
+ [RightScale::RouterClient::NORMAL_CLOSE, RightScale::RouterClient::SHUTDOWN_CLOSE].each do |code|
423
+ it "chooses to connect immediately if previous close code is #{code}" do
424
+ @client.instance_variable_set(:@close_code, code)
425
+ @client.instance_variable_set(:@connect_interval, 300)
426
+ @client.send(:choose_listen_method).should be true
427
+ @client.instance_variable_get(:@listen_state).should == :connect
428
+ @client.instance_variable_get(:@listen_interval).should == 0
429
+ end
430
+ end
431
+
432
+ [502, 503].each do |code|
433
+ it "chooses to connect immediately if previous close code #{code} indicates router not responding" do
434
+ @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
435
+ @client.instance_variable_set(:@close_reason, "Unexpected response code: #{code}")
436
+ @client.instance_variable_set(:@connect_interval, 300)
437
+ @client.send(:choose_listen_method).should be true
438
+ @client.instance_variable_get(:@listen_state).should == :connect
439
+ @client.instance_variable_get(:@listen_interval).should == 0
440
+ end
441
+ end
442
+
443
+ it "otherwise it chooses to connect as soon as connect interval expires" do
444
+ @client.instance_variable_set(:@attempted_connect_at, @now - 28)
445
+ @client.send(:choose_listen_method).should be true
446
+ @client.instance_variable_get(:@listen_state).should == :connect
447
+ @client.instance_variable_get(:@listen_interval).should == 1
448
+ end
449
+ end
292
450
  end
451
+ end
293
452
 
294
- it "loops instead of long-polling if websocket creation was unsuccessful" do
295
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
296
- flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil); @client.close }
297
- flexmock(@client).should_receive(:long_poll).never
298
- @client.send(:try_connect, @routing_keys, &@handler)
453
+ context :try_connect do
454
+ before(:each) do
455
+ @client.instance_variable_get(:@websocket).should be nil
456
+ @client.instance_variable_set(:@connect_interval, 30)
457
+ @client.instance_variable_set(:@reconnect_interval, 2)
299
458
  end
300
459
 
301
- it "sleeps after successfully creating websocket" do
302
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
303
- flexmock(@client).should_receive(:sleep).with(1).times(4).ordered
304
- flexmock(@client).should_receive(:sleep).with(1).and_return { @client.close }.once.ordered
460
+ it "makes websocket connect request and sets state to :check" do
461
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
305
462
  @client.send(:try_connect, @routing_keys, &@handler)
463
+ @client.instance_variable_get(:@listen_state).should == :check
464
+ @client.instance_variable_get(:@listen_interval).should == 1
306
465
  end
307
466
 
308
- it "adjusts connect interval if websocket creation fails" do
309
- @log.should_receive(:error).with("Failed creating WebSocket", StandardError).once
310
- flexmock(Faye::WebSocket::Client).should_receive(:new).and_raise(StandardError).once
311
- flexmock(@client).should_receive(:sleep).never
467
+ it "adjusts connect interval if websocket creation fails and sets state to :long_poll" do
468
+ @log.should_receive(:error).with("Failed creating WebSocket", RuntimeError).once
469
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_raise(RuntimeError).once
312
470
  @client.send(:try_connect, @routing_keys, &@handler)
313
471
  @client.instance_variable_get(:@connect_interval).should == 60
472
+ @client.instance_variable_get(:@listen_state).should == :long_poll
473
+ @client.instance_variable_get(:@listen_interval).should == 0
314
474
  end
315
475
  end
316
476
 
@@ -322,7 +482,7 @@ describe RightScale::RouterClient do
322
482
  context "when creating connection" do
323
483
  it "connects to router" do
324
484
  flexmock(Faye::WebSocket::Client).should_receive(:new).with(@ws_url + "/connect", nil, Hash).and_return(@websocket).once
325
- @client.send(:connect, @routing_keys) { |_| }
485
+ @client.send(:connect, @routing_keys, &@handler)
326
486
  end
327
487
 
328
488
  it "chooses scheme based on scheme in router URL" do
@@ -331,29 +491,29 @@ describe RightScale::RouterClient do
331
491
  @auth_client = AuthClientMock.new(@url, @auth_header, :authorized)
332
492
  @client = RightScale::RouterClient.new(@auth_client, @options)
333
493
  flexmock(Faye::WebSocket::Client).should_receive(:new).with(@ws_url + "/connect", nil, Hash).and_return(@websocket).once
334
- @client.send(:connect, @routing_keys) { |_| }
494
+ @client.send(:connect, @routing_keys, &@handler)
335
495
  end
336
496
 
337
497
  it "uses headers containing only API version and authorization" do
338
498
  headers = @auth_header.merge("X-API-Version" => "2.0")
339
499
  flexmock(Faye::WebSocket::Client).should_receive(:new).with(String, nil, hsh(:headers => headers)).and_return(@websocket).once
340
- @client.send(:connect, @routing_keys) { |_| }
500
+ @client.send(:connect, @routing_keys, &@handler)
341
501
  end
342
502
 
343
503
  it "enables ping" do
344
504
  flexmock(Faye::WebSocket::Client).should_receive(:new).with(String, nil, hsh(:ping => 60)).and_return(@websocket).once
345
- @client.send(:connect, @routing_keys) { |_| }
505
+ @client.send(:connect, @routing_keys, &@handler)
346
506
  end
347
507
 
348
508
  it "adds routing keys as query parameters" do
349
509
  url = @ws_url + "/connect" + "?routing_keys[]=a%3Ab%3Dc"
350
510
  flexmock(Faye::WebSocket::Client).should_receive(:new).with(url, nil, Hash).and_return(@websocket).once
351
- @client.send(:connect, ["a:b=c"]) { |_| }
511
+ @client.send(:connect, ["a:b=c"], &@handler)
352
512
  end
353
513
 
354
514
  it "returns websocket" do
355
515
  flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
356
- @client.send(:connect, @routing_keys) { |_| }.should == @websocket
516
+ @client.send(:connect, @routing_keys, &@handler).should == @websocket
357
517
  @client.instance_variable_get(:@websocket).should == @websocket
358
518
  end
359
519
  end
@@ -374,7 +534,6 @@ describe RightScale::RouterClient do
374
534
  it "logs event" do
375
535
  @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
376
536
  @log.should_receive(:info).with("Received EVENT <uuid> Push /foo/bar from rs-agent-1-1").once.ordered
377
- @log.should_receive(:info).with("Sending EVENT <uuid> Push /foo/bar to rs-agent-1-1").once.ordered
378
537
  event = nil
379
538
  @client.send(:connect, @routing_keys) { |e| event = e }
380
539
  @websocket.onmessage(@json_event)
@@ -387,19 +546,6 @@ describe RightScale::RouterClient do
387
546
  @websocket.sent.should == @json_ack
388
547
  end
389
548
 
390
- it "sends event response using websocket" do
391
- result = {:uuid => "uuid2", :type => "Result", :from => "rs-agent-2-2", :data => {}, :version => @version}
392
- @client.send(:connect, @routing_keys) { |_| result }
393
- @websocket.onmessage(@json_event)
394
- @websocket.sent.should == [@json_ack, JSON.dump({:event => result, :routing_keys => ["rs-agent-1-1"]})]
395
- end
396
-
397
- it "only sends non-nil responses" do
398
- @client.send(:connect, @routing_keys) { |_| nil }
399
- @websocket.onmessage(@json_event)
400
- @websocket.sent.should == @json_ack
401
- end
402
-
403
549
  it "logs failures" do
404
550
  @log.should_receive(:error).with("Failed handling WebSocket event", StandardError, :trace).once
405
551
  request = RightScale::Request.new("/foo/bar", "payload")
@@ -412,15 +558,15 @@ describe RightScale::RouterClient do
412
558
  it "logs info" do
413
559
  @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
414
560
  @log.should_receive(:info).with("WebSocket closed (1000)").once.ordered
415
- @client.send(:connect, @routing_keys) { |_| }
561
+ @client.send(:connect, @routing_keys, &@handler)
416
562
  @websocket.onclose(1000)
417
- @client.instance_variable_get(:@websocket).should be_nil
563
+ @client.instance_variable_get(:@websocket).should be nil
418
564
  end
419
565
 
420
566
  it "logged info includes reason if available" do
421
567
  @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
422
568
  @log.should_receive(:info).with("WebSocket closed (1001: Going Away)").once.ordered
423
- @client.send(:connect, @routing_keys) { |_| }
569
+ @client.send(:connect, @routing_keys, &@handler)
424
570
  @websocket.onclose(1001, "Going Away")
425
571
  end
426
572
 
@@ -428,7 +574,7 @@ describe RightScale::RouterClient do
428
574
  @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
429
575
  @log.should_receive(:info).and_raise(RuntimeError).once.ordered
430
576
  @log.should_receive(:error).with("Failed closing WebSocket", RuntimeError, :trace).once
431
- @client.send(:connect, @routing_keys) { |_| }
577
+ @client.send(:connect, @routing_keys, &@handler)
432
578
  @websocket.onclose(1000)
433
579
  end
434
580
  end
@@ -436,13 +582,13 @@ describe RightScale::RouterClient do
436
582
  context "on error" do
437
583
  it "logs error" do
438
584
  @log.should_receive(:error).with("WebSocket error (Protocol Error)")
439
- @client.send(:connect, @routing_keys) { |_| }
585
+ @client.send(:connect, @routing_keys, &@handler)
440
586
  @websocket.onerror("Protocol Error")
441
587
  end
442
588
 
443
589
  it "does not log if there is no error data" do
444
590
  @log.should_receive(:error).never
445
- @client.send(:connect, @routing_keys) { |_| }
591
+ @client.send(:connect, @routing_keys, &@handler)
446
592
  @websocket.onerror(nil)
447
593
  end
448
594
  end
@@ -450,37 +596,70 @@ describe RightScale::RouterClient do
450
596
 
451
597
  context :try_long_poll do
452
598
  before(:each) do
453
- @uuids = ["uuid"]
454
- @handler = lambda { |_| }
599
+ @event_uuids = ["uuid"]
455
600
  @client.instance_variable_set(:@connect_interval, 30)
456
601
  @client.instance_variable_set(:@reconnect_interval, 2)
457
602
  end
458
603
 
459
604
  it "makes long-polling request" do
460
- flexmock(@client).should_receive(:long_poll).with(@routing_keys, @uuids, @handler).and_return([]).once
461
- @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should == []
605
+ flexmock(@client).should_receive(:long_poll).with(@routing_keys, @event_uuids, @handler).and_return([]).once
606
+ @client.send(:try_long_poll, @routing_keys, @event_uuids, &@handler).should == []
462
607
  end
463
608
 
464
609
  it "returns UUIDs of events received" do
465
- flexmock(@client).should_receive(:long_poll).with(@routing_keys, [], @handler).and_return { @uuids }.once
466
- @client.send(:try_long_poll, @routing_keys, [], &@handler).should == @uuids
610
+ flexmock(@client).should_receive(:long_poll).with(@routing_keys, [], @handler).and_return { @event_uuids }.once
611
+ @client.send(:try_long_poll, @routing_keys, [], &@handler).should == @event_uuids
612
+ end
613
+
614
+ it "returns exception if there is a long-polling failure" do
615
+ flexmock(@client).should_receive(:long_poll).and_raise(RuntimeError).once
616
+ @client.send(:try_long_poll, @routing_keys, @event_uuids, &@handler).should be_a RuntimeError
467
617
  end
618
+ end
468
619
 
469
- it "sleeps if there is a long-polling failure" do
470
- @log.should_receive(:error).with("Failed long-polling", StandardError, :trace).once
471
- flexmock(@client).should_receive(:long_poll).and_raise(StandardError).once
472
- flexmock(@client).should_receive(:sleep).with(4).once
473
- @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should be_nil
620
+ context :try_deferred_long_poll do
621
+ before(:each) do
622
+ @event_uuids = ["uuid"]
623
+ @client.instance_variable_set(:@connect_interval, 30)
624
+ @client.instance_variable_set(:@reconnect_interval, 2)
625
+ @client.send(:update_listen_state, :long_poll)
626
+ flexmock(EM).should_receive(:defer).by_default
474
627
  end
475
628
 
476
- [RightScale::Exceptions::Unauthorized,
477
- RightScale::Exceptions::ConnectivityFailure,
478
- RightScale::Exceptions::RetryableError].each do |e|
479
- it "does not trace #{e} exceptions" do
480
- @log.should_receive(:error).with("Failed long-polling", e, :no_trace).once
481
- flexmock(@client).should_receive(:long_poll).and_raise(e, "failed").once
482
- flexmock(@client).should_receive(:sleep).with(4).once
483
- @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should be_nil
629
+ it "makes long-polling request using defer thread" do
630
+ flexmock(EM).should_receive(:defer).with(Proc, Proc).once
631
+ @client.send(:try_deferred_long_poll, @routing_keys, @event_uuids, &@handler).should be true
632
+ end
633
+
634
+ context "defer_operation_proc" do
635
+ it "long-polls" do
636
+ @client.instance_variable_set(:@connect_interval, 1)
637
+ @client.send(:try_deferred_long_poll, @routing_keys, @event_uuids, &@handler)
638
+ @defer_operation_proc = @client.instance_variable_get(:@defer_operation_proc)
639
+ flexmock(@client).should_receive(:long_poll).with(@routing_keys, @event_uuids, @handler).and_return([]).once
640
+ @defer_operation_proc.call.should == []
641
+ end
642
+ end
643
+
644
+ context "defer_callback_proc" do
645
+ before(:each) do
646
+ @client.instance_variable_set(:@connect_interval, 1)
647
+ @client.send(:try_deferred_long_poll, @routing_keys, @event_uuids, &@handler)
648
+ @defer_callback_proc = @client.instance_variable_get(:@defer_callback_proc)
649
+ end
650
+
651
+ it "handles UUIDs of events received" do
652
+ @defer_callback_proc.call(@event_uuids).should == @event_uuids
653
+ @client.instance_variable_get(:@event_uuids).should == @event_uuids
654
+ @client.instance_variable_get(:@listen_state).should == :choose
655
+ @client.instance_variable_get(:@listen_interval).should == 0
656
+ end
657
+
658
+ it "handles exception if there is a long-polling failure" do
659
+ @log.should_receive(:error).with("Failed long-polling", RuntimeError, :trace).once
660
+ @defer_callback_proc.call(RuntimeError.new).should be nil
661
+ @client.instance_variable_get(:@listen_state).should == :choose
662
+ @client.instance_variable_get(:@listen_interval).should == 4
484
663
  end
485
664
  end
486
665
  end
@@ -495,22 +674,23 @@ describe RightScale::RouterClient do
495
674
  end
496
675
 
497
676
  it "makes listen request to router" do
498
- flexmock(@client).should_receive(:make_request).with(:get, "/listen",
677
+ flexmock(@client).should_receive(:make_request).with(:poll, "/listen",
499
678
  on { |a| a[:wait_time].should == 55 && !a.key?(:routing_keys) &&
500
679
  a[:timestamp] == @later.to_f }, "listen", nil, Hash).and_return([@event]).once
501
- @client.send(:long_poll, @routing_keys, @ack) { |_| }
680
+ @client.send(:long_poll, @routing_keys, @ack, &@handler)
502
681
  end
503
682
 
504
- it "uses listen timeout for request" do
505
- flexmock(@client).should_receive(:make_request).with(:get, "/listen", Hash, "listen", nil,
506
- {:request_timeout => 60, :log_level => :debug}).and_return([@event]).once
507
- @client.send(:long_poll, @routing_keys, @ack) { |_| }
683
+ it "uses listen timeout for request poll timeout and connect interval for request timeout" do
684
+ @client.instance_variable_set(:@connect_interval, 300)
685
+ flexmock(@client).should_receive(:make_request).with(:poll, "/listen", Hash, "listen", nil,
686
+ {:poll_timeout => 60, :request_timeout => 300, :log_level => :debug}).and_return([@event]).once
687
+ @client.send(:long_poll, @routing_keys, @ack, &@handler)
508
688
  end
509
689
 
510
690
  it "logs event" do
511
691
  @log.should_receive(:info).with("Received EVENT <uuid> Push /foo/bar from rs-agent-1-1").once
512
692
  flexmock(@client).should_receive(:make_request).and_return([@event])
513
- @client.send(:long_poll, @routing_keys, @ack) { |_| }
693
+ @client.send(:long_poll, @routing_keys, @ack, &@handler)
514
694
  end
515
695
 
516
696
  it "presents event to handler" do
@@ -533,18 +713,57 @@ describe RightScale::RouterClient do
533
713
  flexmock(@client).should_receive(:make_request).and_return(nil)
534
714
  event = nil
535
715
  @client.send(:long_poll, @routing_keys, @ack) { |e| event = e }
536
- event.should be_nil
716
+ event.should be nil
537
717
  end
538
718
 
539
719
  it "returns event UUIDs" do
540
720
  flexmock(@client).should_receive(:make_request).and_return([@event])
541
- @client.send(:long_poll, @routing_keys, @ack) { |_| }.should == ["uuid"]
721
+ @client.send(:long_poll, @routing_keys, @ack, &@handler).should == ["uuid"]
542
722
  end
543
723
 
544
724
  it "returns nil if no events are received" do
545
725
  flexmock(@client).should_receive(:make_request).and_return(nil)
546
- @client.send(:long_poll, @routing_keys, @ack) { |_| }.should be_nil
547
- end
726
+ @client.send(:long_poll, @routing_keys, @ack, &@handler).should be nil
727
+ end
728
+ end
729
+
730
+ context :process_long_poll do
731
+ before(:each) do
732
+ @event_uuids = ["uuid"]
733
+ @client.instance_variable_set(:@connect_interval, 30)
734
+ @client.instance_variable_set(:@reconnect_interval, 2)
735
+ @client.send(:update_listen_state, :long_poll)
736
+ end
737
+
738
+ [RightScale::Exceptions::Unauthorized.new("error"),
739
+ RightScale::Exceptions::ConnectivityFailure.new("error"),
740
+ RightScale::Exceptions::RetryableError.new("error"),
741
+ RightScale::Exceptions::InternalServerError.new("error", "server")].each do |e|
742
+ it "does not trace #{e} exceptions but sets state to :choose" do
743
+ @log.should_receive(:error).with("Failed long-polling", e, :no_trace).once
744
+ @client.send(:process_long_poll, e).should be nil
745
+ @client.instance_variable_get(:@listen_state).should == :choose
746
+ @client.instance_variable_get(:@listen_interval).should == 4
747
+ end
748
+ end
749
+
750
+ it "traces unexpected exceptions and sets state to :choose" do
751
+ e = RuntimeError.new
752
+ @log.should_receive(:error).with("Failed long-polling", e, :trace).once
753
+ @client.send(:process_long_poll, e).should be nil
754
+ @client.instance_variable_get(:@listen_state).should == :choose
755
+ @client.instance_variable_get(:@listen_interval).should == 4
756
+ end
757
+
758
+ context "when no exception" do
759
+ it "sets state to :choose" do
760
+ @client.instance_variable_set(:@reconnect_interval, 2)
761
+ @client.send(:process_long_poll, @event_uuids).should == @event_uuids
762
+ @client.instance_variable_get(:@listen_state).should == :choose
763
+ @client.instance_variable_get(:@reconnect_interval).should == 2
764
+ @client.instance_variable_get(:@listen_interval).should == 0
765
+ end
766
+ end
548
767
  end
549
768
 
550
769
  context :backoff_connect_interval do
@@ -587,7 +806,7 @@ describe RightScale::RouterClient do
587
806
  it "does not declare not responding for other close codes" do
588
807
  @client.instance_variable_set(:@close_code, RightScale::RouterClient::UNEXPECTED_ERROR_CLOSE)
589
808
  @client.instance_variable_set(:@close_reason, "Unexpected response code: 502")
590
- @client.send(:router_not_responding?).should be_false
809
+ @client.send(:router_not_responding?).should be false
591
810
  end
592
811
  end
593
812
  end