right_agent 2.0.8-x86-mingw32 → 2.1.0-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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