right_agent 1.0.1 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README.rdoc +10 -8
  2. data/Rakefile +31 -5
  3. data/lib/right_agent.rb +6 -1
  4. data/lib/right_agent/actor.rb +4 -20
  5. data/lib/right_agent/actors/agent_manager.rb +1 -1
  6. data/lib/right_agent/agent.rb +357 -144
  7. data/lib/right_agent/agent_config.rb +7 -6
  8. data/lib/right_agent/agent_identity.rb +13 -11
  9. data/lib/right_agent/agent_tag_manager.rb +60 -64
  10. data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
  11. data/lib/right_agent/clients/api_client.rb +383 -0
  12. data/lib/right_agent/clients/auth_client.rb +247 -0
  13. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  14. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  15. data/lib/right_agent/clients/right_http_client.rb +279 -0
  16. data/lib/right_agent/clients/router_client.rb +493 -0
  17. data/lib/right_agent/command/command_io.rb +4 -4
  18. data/lib/right_agent/command/command_parser.rb +2 -2
  19. data/lib/right_agent/command/command_runner.rb +1 -1
  20. data/lib/right_agent/connectivity_checker.rb +179 -0
  21. data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
  22. data/lib/right_agent/dispatcher.rb +12 -10
  23. data/lib/right_agent/enrollment_result.rb +16 -12
  24. data/lib/right_agent/exceptions.rb +34 -20
  25. data/lib/right_agent/history.rb +10 -5
  26. data/lib/right_agent/log.rb +5 -5
  27. data/lib/right_agent/minimal.rb +1 -0
  28. data/lib/right_agent/multiplexer.rb +1 -1
  29. data/lib/right_agent/offline_handler.rb +270 -0
  30. data/lib/right_agent/packets.rb +7 -7
  31. data/lib/right_agent/payload_formatter.rb +1 -1
  32. data/lib/right_agent/pending_requests.rb +128 -0
  33. data/lib/right_agent/platform.rb +1 -1
  34. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  35. data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
  36. data/lib/right_agent/scripts/agent_controller.rb +28 -26
  37. data/lib/right_agent/scripts/agent_deployer.rb +37 -22
  38. data/lib/right_agent/scripts/common_parser.rb +10 -3
  39. data/lib/right_agent/secure_identity.rb +1 -1
  40. data/lib/right_agent/sender.rb +299 -785
  41. data/lib/right_agent/serialize/secure_serializer.rb +3 -1
  42. data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
  43. data/lib/right_agent/serialize/serializable.rb +8 -3
  44. data/right_agent.gemspec +49 -18
  45. data/spec/agent_config_spec.rb +7 -7
  46. data/spec/agent_identity_spec.rb +7 -4
  47. data/spec/agent_spec.rb +43 -7
  48. data/spec/agent_tag_manager_spec.rb +72 -83
  49. data/spec/clients/api_client_spec.rb +423 -0
  50. data/spec/clients/auth_client_spec.rb +272 -0
  51. data/spec/clients/balanced_http_client_spec.rb +576 -0
  52. data/spec/clients/base_retry_client_spec.rb +635 -0
  53. data/spec/clients/router_client_spec.rb +594 -0
  54. data/spec/clients/spec_helper.rb +111 -0
  55. data/spec/command/command_io_spec.rb +1 -1
  56. data/spec/command/command_parser_spec.rb +1 -1
  57. data/spec/connectivity_checker_spec.rb +83 -0
  58. data/spec/dispatcher_spec.rb +3 -2
  59. data/spec/enrollment_result_spec.rb +2 -2
  60. data/spec/history_spec.rb +51 -39
  61. data/spec/offline_handler_spec.rb +340 -0
  62. data/spec/pending_requests_spec.rb +136 -0
  63. data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
  64. data/spec/sender_spec.rb +835 -1052
  65. data/spec/serialize/secure_serializer_spec.rb +3 -2
  66. data/spec/spec_helper.rb +54 -1
  67. metadata +71 -12
@@ -22,7 +22,7 @@
22
22
 
23
23
  require File.join(File.dirname(__FILE__), 'spec_helper')
24
24
 
25
- describe RightScale::IdempotentRequest do
25
+ describe RightScale::RetryableRequest do
26
26
 
27
27
  module RightScale
28
28
  class SenderMock
@@ -47,37 +47,37 @@ describe RightScale::IdempotentRequest do
47
47
 
48
48
  context 'when :targets => nil' do
49
49
  it 'should send target-less requests' do
50
- request = RightScale::IdempotentRequest.new('type', 'payload')
51
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
50
+ request = RightScale::RetryableRequest.new('type', 'payload')
51
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
52
52
  and_yield(RightScale::OperationResult.non_delivery('test')).once
53
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
54
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
53
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
54
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
55
55
  request.run
56
56
  end
57
57
  end
58
58
 
59
59
  context 'when one target is specified' do
60
60
  it 'should send a targeted request' do
61
- request = RightScale::IdempotentRequest.new('type', 'payload', :targets => [1])
62
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', 1, Proc).
61
+ request = RightScale::RetryableRequest.new('type', 'payload', :targets => ["rs-agent-1-1"])
62
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', {:agent_id => "rs-agent-1-1"}, Proc).
63
63
  and_yield(RightScale::OperationResult.non_delivery('test')).once
64
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
65
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
64
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
65
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
66
66
  request.run
67
67
  end
68
68
  end
69
69
 
70
70
  context 'when many targets are specified' do
71
71
  it 'should choose a random target' do
72
- request = RightScale::IdempotentRequest.new('type', 'payload', :targets => [1, 2, 3])
73
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).and_return do |type, payload, tgt, block|
72
+ request = RightScale::RetryableRequest.new('type', 'payload', :targets => ["rs-agent-1-1", "rs-agent-2-2", "rs-agent-3-3"])
73
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).and_return do |type, payload, target, block|
74
74
  type.should == 'type'
75
75
  payload.should == 'payload'
76
- [1, 2, 3].should include(tgt)
76
+ ["rs-agent-1-1", "rs-agent-2-2", "rs-agent-3-3"].should include(target[:agent_id])
77
77
  block.call(RightScale::OperationResult.non_delivery('test'))
78
78
  end
79
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
80
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
79
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
80
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
81
81
  request.run
82
82
  end
83
83
  end
@@ -88,44 +88,44 @@ describe RightScale::IdempotentRequest do
88
88
 
89
89
  context 'when not specified' do
90
90
  it 'should fail if receives error response' do
91
- request = RightScale::IdempotentRequest.new('type', 'payload')
92
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
91
+ request = RightScale::RetryableRequest.new('type', 'payload')
92
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
93
93
  and_yield(RightScale::OperationResult.error('test')).once
94
94
  flexmock(request).should_receive(:fail).once
95
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
95
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
96
96
  request.run
97
97
  end
98
98
  end
99
99
 
100
100
  context 'when specified as true' do
101
101
  it 'should retry if receives error response' do
102
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_on_error => true)
103
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
102
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_on_error => true)
103
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
104
104
  and_yield(RightScale::OperationResult.error('test')).once
105
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
106
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
105
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
106
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
107
107
  request.run
108
108
  end
109
109
 
110
110
  it 'should ignore duplicate responses' do
111
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_on_error => true)
112
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).and_return do |t, p, tgt, b|
111
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_on_error => true)
112
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).and_return do |t, p, tgt, b|
113
113
  5.times { b.call(RightScale::OperationResult.success('test')) }
114
114
  end
115
115
  flexmock(request).should_receive(:fail).never
116
116
  flexmock(request).should_receive(:succeed).once
117
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).never
118
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
117
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).never
118
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
119
119
  request.run
120
120
  end
121
121
 
122
122
  it 'should never retry after cancel response' do
123
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_on_error => true)
123
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_on_error => true)
124
124
  flexmock(RightScale::Log).should_receive(:info).with("Request type canceled (enough already)").once
125
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
125
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
126
126
  and_yield(RightScale::OperationResult.cancel('enough already')).once
127
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).never
128
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
127
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).never
128
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
129
129
  request.run
130
130
  end
131
131
  end
@@ -136,57 +136,57 @@ describe RightScale::IdempotentRequest do
136
136
 
137
137
  context 'when using default settings' do
138
138
  it 'should retry non-delivery responses' do
139
- request = RightScale::IdempotentRequest.new('type', 'payload')
139
+ request = RightScale::RetryableRequest.new('type', 'payload')
140
140
  flexmock(RightScale::Log).should_receive(:info).with(/Retrying in 5 seconds/).once
141
141
  flexmock(RightScale::Log).should_receive(:info).with("Request non-delivery (test) for type").once
142
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
142
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
143
143
  and_yield(RightScale::OperationResult.non_delivery('test')).once
144
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
145
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
144
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
145
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
146
146
  request.run
147
147
  end
148
148
 
149
149
  it 'should retry retry responses' do
150
- request = RightScale::IdempotentRequest.new('type', 'payload')
150
+ request = RightScale::RetryableRequest.new('type', 'payload')
151
151
  flexmock(RightScale::Log).should_receive(:info).with(/Retrying in 5 seconds/).once
152
152
  flexmock(RightScale::Log).should_receive(:info).with("Request type failed (test) and should be retried").once
153
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
153
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
154
154
  and_yield(RightScale::OperationResult.retry('test')).once
155
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
156
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
155
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
156
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
157
157
  request.run
158
158
  end
159
159
 
160
160
  it 'should log default retry reason if none given' do
161
- request = RightScale::IdempotentRequest.new('type', 'payload')
161
+ request = RightScale::RetryableRequest.new('type', 'payload')
162
162
  flexmock(RightScale::Log).should_receive(:info).with(/Retrying in 5 seconds/).once
163
163
  flexmock(RightScale::Log).should_receive(:info).with("Request type failed (RightScale not ready) and should be retried").once
164
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
164
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
165
165
  and_yield(RightScale::OperationResult.retry).once
166
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).once
167
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
166
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).once
167
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
168
168
  request.run
169
169
  end
170
170
 
171
171
  it 'should ignore responses that arrive post-cancel' do
172
- request = RightScale::IdempotentRequest.new('type', 'payload')
173
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
172
+ request = RightScale::RetryableRequest.new('type', 'payload')
173
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
174
174
  and_yield(RightScale::OperationResult.success('test')).once
175
175
  flexmock(request).should_receive(:fail).once
176
176
  flexmock(request).should_receive(:succeed).never
177
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).never
178
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
177
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).never
178
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
179
179
  request.cancel('test')
180
180
  request.run
181
181
  end
182
182
 
183
183
  it 'should never retry after cancel response' do
184
- request = RightScale::IdempotentRequest.new('type', 'payload')
184
+ request = RightScale::RetryableRequest.new('type', 'payload')
185
185
  flexmock(RightScale::Log).should_receive(:info).with("Request type canceled (enough already)").once
186
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
186
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
187
187
  and_yield(RightScale::OperationResult.cancel('enough already')).once
188
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_RETRY_DELAY, Proc).never
189
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
188
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_RETRY_DELAY, Proc).never
189
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
190
190
  request.run
191
191
  end
192
192
  end
@@ -194,22 +194,22 @@ describe RightScale::IdempotentRequest do
194
194
  context 'when a :retry_delay is specified' do
195
195
  it 'should control the initial retry delay' do
196
196
  retry_delay = 10
197
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_delay => retry_delay)
198
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
197
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_delay => retry_delay)
198
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
199
199
  and_yield(RightScale::OperationResult.retry('test')).once
200
200
  flexmock(EM).should_receive(:add_timer).with(retry_delay, Proc).once
201
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
201
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
202
202
  flexmock(EM).should_receive(:next_tick).never
203
203
  request.run
204
204
  end
205
205
 
206
206
  it 'should treat -1 as meaning no delay' do
207
207
  retry_delay = -1
208
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_delay => retry_delay)
209
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
208
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_delay => retry_delay)
209
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
210
210
  and_yield(RightScale::OperationResult.retry('test')).once
211
211
  flexmock(EM).should_receive(:add_timer).with(retry_delay, Proc).never
212
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
212
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
213
213
  flexmock(EM).should_receive(:next_tick).once
214
214
  request.run
215
215
  end
@@ -219,14 +219,14 @@ describe RightScale::IdempotentRequest do
219
219
  it 'should limit the number of retries using the :retry_delay value' do
220
220
  retry_delay = 10
221
221
  retry_delay_count = 1
222
- backoff_factor = RightScale::IdempotentRequest::RETRY_BACKOFF_FACTOR
223
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_delay => retry_delay,
222
+ backoff_factor = RightScale::RetryableRequest::RETRY_BACKOFF_FACTOR
223
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_delay => retry_delay,
224
224
  :retry_delay_count => retry_delay_count)
225
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
225
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
226
226
  and_yield(RightScale::OperationResult.retry('test')).twice
227
227
  flexmock(EM).should_receive(:add_timer).with(retry_delay, Proc).and_yield.once
228
228
  flexmock(EM).should_receive(:add_timer).with(retry_delay * backoff_factor, Proc).once
229
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
229
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
230
230
  flexmock(EM).should_receive(:next_tick).never
231
231
  request.run
232
232
  end
@@ -235,16 +235,16 @@ describe RightScale::IdempotentRequest do
235
235
  retry_delay = 10
236
236
  retry_delay_count = 2
237
237
  max_retry_delay = 30
238
- backoff_factor = RightScale::IdempotentRequest::RETRY_BACKOFF_FACTOR
239
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_delay => retry_delay,
238
+ backoff_factor = RightScale::RetryableRequest::RETRY_BACKOFF_FACTOR
239
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_delay => retry_delay,
240
240
  :retry_delay_count => retry_delay_count,
241
241
  :max_retry_delay => max_retry_delay)
242
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
242
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
243
243
  and_yield(RightScale::OperationResult.retry('test')).times(4)
244
244
  flexmock(EM).should_receive(:add_timer).with(retry_delay, Proc).and_yield.twice
245
245
  flexmock(EM).should_receive(:add_timer).with(retry_delay * backoff_factor, Proc).and_yield.once
246
246
  flexmock(EM).should_receive(:add_timer).with(max_retry_delay, Proc).once
247
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
247
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
248
248
  flexmock(EM).should_receive(:next_tick).never
249
249
  request.run
250
250
  request.instance_variable_get(:@retry_delay_count).should == retry_delay_count / 2
@@ -256,16 +256,16 @@ describe RightScale::IdempotentRequest do
256
256
  retry_delay = 10
257
257
  retry_delay_count = 1
258
258
  max_retry_delay = 30
259
- backoff_factor = RightScale::IdempotentRequest::RETRY_BACKOFF_FACTOR
260
- request = RightScale::IdempotentRequest.new('type', 'payload', :retry_delay => retry_delay,
259
+ backoff_factor = RightScale::RetryableRequest::RETRY_BACKOFF_FACTOR
260
+ request = RightScale::RetryableRequest.new('type', 'payload', :retry_delay => retry_delay,
261
261
  :retry_delay_count => retry_delay_count,
262
262
  :max_retry_delay => max_retry_delay)
263
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).
263
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).
264
264
  and_yield(RightScale::OperationResult.retry('test')).times(3)
265
265
  flexmock(EM).should_receive(:add_timer).with(retry_delay, Proc).and_yield.once
266
266
  flexmock(EM).should_receive(:add_timer).with(retry_delay * backoff_factor, Proc).and_yield.once
267
267
  flexmock(EM).should_receive(:add_timer).with(max_retry_delay, Proc).once
268
- flexmock(EM).should_receive(:add_timer).with(RightScale::IdempotentRequest::DEFAULT_TIMEOUT, Proc).once
268
+ flexmock(EM).should_receive(:add_timer).with(RightScale::RetryableRequest::DEFAULT_TIMEOUT, Proc).once
269
269
  flexmock(EM).should_receive(:next_tick).never
270
270
  request.run
271
271
  end
@@ -276,8 +276,8 @@ describe RightScale::IdempotentRequest do
276
276
  context ':timeout option' do
277
277
  context 'when disable timeout' do
278
278
  it 'should not timeout' do
279
- request = RightScale::IdempotentRequest.new('type', 'payload', :timeout => -1)
280
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).once
279
+ request = RightScale::RetryableRequest.new('type', 'payload', :timeout => -1)
280
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).once
281
281
  flexmock(EM).should_receive(:add_timer).never
282
282
  request.run
283
283
  end
@@ -286,17 +286,17 @@ describe RightScale::IdempotentRequest do
286
286
  context 'when a timeout is specified' do
287
287
  it 'should time the response' do
288
288
  timeout = 10
289
- request = RightScale::IdempotentRequest.new('type', 'payload', :timeout => timeout)
290
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).once
289
+ request = RightScale::RetryableRequest.new('type', 'payload', :timeout => timeout)
290
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).once
291
291
  flexmock(EM).should_receive(:add_timer).with(timeout, Proc).once
292
292
  request.run
293
293
  end
294
294
 
295
295
  it 'should log a message when timeout' do
296
296
  timeout = 10
297
- request = RightScale::IdempotentRequest.new('type', 'payload', :timeout => timeout)
297
+ request = RightScale::RetryableRequest.new('type', 'payload', :timeout => timeout)
298
298
  flexmock(RightScale::Log).should_receive(:info).with("Request type timed out after 10 seconds").once
299
- flexmock(RightScale::Sender.instance).should_receive(:send_retryable_request).with('type', 'payload', nil, Proc).once
299
+ flexmock(RightScale::Sender.instance).should_receive(:send_request).with('type', 'payload', nil, Proc).once
300
300
  flexmock(EM).should_receive(:add_timer).with(timeout, Proc).and_yield.once
301
301
  request.run
302
302
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2012 RightScale Inc
2
+ # Copyright (c) 2009-2013 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -26,1237 +26,1020 @@ describe RightScale::Sender do
26
26
 
27
27
  include FlexMock::ArgumentTypes
28
28
 
29
- before(:each) do
30
- @log = flexmock(RightScale::Log)
31
- @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
32
- @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
33
- @timer = flexmock("timer", :cancel => true).by_default
34
- end
35
-
36
- describe "when fetching the instance" do
29
+ context "access instance" do
37
30
  before do
38
31
  RightScale::Sender.class_eval do
39
- if class_variable_defined?(:@@instance)
40
- remove_class_variable(:@@instance)
41
- end
32
+ remove_class_variable(:@@instance) if class_variable_defined?(:@@instance)
42
33
  end
43
34
  end
44
-
45
- it "should return nil when the instance is undefined" do
35
+
36
+ it "returns nil when the instance is undefined" do
46
37
  RightScale::Sender.instance.should == nil
47
38
  end
48
-
49
- it "should return the instance if defined" do
50
- instance = flexmock
51
- RightScale::Sender.class_eval do
52
- @@instance = "instance"
53
- end
54
-
39
+
40
+ it "returns the instance if defined" do
41
+ RightScale::Sender.class_eval { @@instance = "instance" }
55
42
  RightScale::Sender.instance.should_not == nil
56
43
  end
57
44
  end
58
45
 
59
- describe "when monitoring broker connectivity" do
60
- before(:each) do
61
- @now = Time.at(1000000)
62
- flexmock(Time).should_receive(:now).and_return(@now).by_default
63
- @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
64
- :identity_parts => ["host", 123, 0, 0]).by_default
65
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
66
- end
67
-
68
- it "should start inactivity timer at initialization time" do
69
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
70
- flexmock(EM::Timer).should_receive(:new).with(1000, Proc).and_return(@timer).once
71
- RightScale::Sender.new(@agent)
72
- end
73
-
74
- it "should not start inactivity timer at initialization time if ping disabled" do
75
- flexmock(EM::Timer).should_receive(:new).never
76
- RightScale::Sender.new(@agent)
77
- end
78
-
79
- it "should restart inactivity timer only if sufficient time has elapsed since last restart" do
80
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
81
- flexmock(EM::Timer).should_receive(:new).with(1000, Proc).and_return(@timer).once
82
- instance = RightScale::Sender.new(@agent)
83
- flexmock(Time).should_receive(:now).and_return(@now + 61)
84
- flexmock(instance.connectivity_checker).should_receive(:restart_inactivity_timer).once
85
- instance.message_received
86
- instance.message_received
87
- end
88
-
89
- it "should check connectivity if the inactivity timer times out" do
90
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
91
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once.by_default
92
- RightScale::Sender.new(@agent)
93
- instance = RightScale::Sender.instance
94
- flexmock(Time).should_receive(:now).and_return(@now + 61)
95
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).and_yield.once
96
- flexmock(instance.connectivity_checker).should_receive(:check).once
97
- instance.message_received
98
- end
99
-
100
- it "should check connectivity by sending mapper ping" do
101
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
102
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).twice
103
- RightScale::Sender.new(@agent)
104
- instance = RightScale::Sender.instance
105
- broker_id = "rs-broker-1-1"
106
- flexmock(instance).should_receive(:publish).with(on do |request|
107
- request.type.should == "/mapper/ping";
108
- request.from.should == "agent"
109
- end, [broker_id]).and_return([broker_id]).once
110
- instance.connectivity_checker.check(broker_id)
111
- instance.pending_requests.size.should == 1
112
- end
113
-
114
- it "should not check connectivity if terminating" do
115
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
116
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
117
- RightScale::Sender.new(@agent)
118
- instance = RightScale::Sender.instance
119
- flexmock(instance).should_receive(:publish).never
120
- instance.terminate
121
- instance.connectivity_checker.check
122
- end
123
-
124
- it "should not check connectivity if not connected to broker" do
125
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
126
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
127
- RightScale::Sender.new(@agent)
128
- broker_id = "rs-broker-1-1"
129
- @broker.should_receive(:connected?).with(broker_id).and_return(false)
130
- instance = RightScale::Sender.instance
131
- flexmock(instance).should_receive(:publish).never
132
- instance.connectivity_checker.check(broker_id)
133
- end
134
-
135
- it "should ignore ping timeout if never successfully publish ping" do
136
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
137
- old_ping_timeout = RightScale::Sender::ConnectivityChecker::PING_TIMEOUT
138
- begin
139
- RightScale::Sender::ConnectivityChecker.const_set(:PING_TIMEOUT, 0.5)
140
- EM.run do
141
- EM.add_timer(1) { EM.stop }
142
- RightScale::Sender.new(@agent)
143
- @instance = RightScale::Sender.instance
144
- flexmock(@instance).should_receive(:publish).and_return([]).once
145
- @instance.connectivity_checker.check(id = nil)
146
- end
147
- ensure
148
- RightScale::Sender::ConnectivityChecker.const_set(:PING_TIMEOUT, old_ping_timeout)
149
- @instance.connectivity_checker.instance_variable_get(:@ping_timer).should be_nil
150
- @instance.connectivity_checker.instance_variable_get(:@ping_id).should be_nil
151
- end
152
- end
153
-
154
- it "should ignore messages received if ping disabled" do
155
- @agent.should_receive(:options).and_return(:ping_interval => 0)
156
- flexmock(EM::Timer).should_receive(:new).never
157
- RightScale::Sender.new(@agent)
158
- RightScale::Sender.instance.message_received
159
- end
160
-
161
- it "should log an exception if the connectivity check fails" do
162
- @log.should_receive(:error).with(/Failed connectivity check/, Exception, :trace).once
163
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
164
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once.by_default
46
+ context "use instance" do
47
+ # Create new sender with specified mode and options
48
+ # Also create mock agent and client for it with basic support for both modes
49
+ def create_sender(mode, options = {})
50
+ @broker_id = "rs-broker-1-1"
51
+ @broker_ids = [@broker_id]
52
+ @client = flexmock("client", :push => true, :request => true, :all => @broker_ids, :publish => @broker_ids).by_default
53
+ @agent_id = "rs-agent-1-1"
54
+ @agent = flexmock("agent", :identity => @agent_id, :client => @client, :mode => mode, :request_queue => "request",
55
+ :options => options).by_default
165
56
  RightScale::Sender.new(@agent)
166
- instance = RightScale::Sender.instance
167
- flexmock(Time).should_receive(:now).and_return(@now + 61)
168
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).and_yield.once
169
- flexmock(instance.connectivity_checker).should_receive(:check).and_raise(Exception)
170
- instance.message_received
57
+ RightScale::Sender.instance
171
58
  end
172
59
 
173
- it "should attempt to reconnect if mapper ping times out" do
174
- @log.should_receive(:error).with(/Mapper ping via broker/).once
175
- @agent.should_receive(:options).and_return(:ping_interval => 1000)
176
- broker_id = "rs-broker-localhost-5672"
177
- @broker.should_receive(:identity_parts).with(broker_id).and_return(["localhost", 5672, 0, 0]).once
178
- @agent.should_receive(:connect).with("localhost", 5672, 0, 0, true).once
179
- old_ping_timeout = RightScale::Sender::ConnectivityChecker::PING_TIMEOUT
180
- old_max_ping_timeouts = RightScale::Sender::ConnectivityChecker::MAX_PING_TIMEOUTS
181
- begin
182
- RightScale::Sender::ConnectivityChecker.const_set(:PING_TIMEOUT, 0.5)
183
- RightScale::Sender::ConnectivityChecker.const_set(:MAX_PING_TIMEOUTS, 1)
184
- EM.run do
185
- EM.add_timer(1) { EM.stop }
186
- RightScale::Sender.new(@agent)
187
- instance = RightScale::Sender.instance
188
- flexmock(instance).should_receive(:publish).with(RightScale::Request, nil).and_return([broker_id])
189
- instance.connectivity_checker.check
190
- end
191
- ensure
192
- RightScale::Sender::ConnectivityChecker.const_set(:PING_TIMEOUT, old_ping_timeout)
193
- RightScale::Sender::ConnectivityChecker.const_set(:MAX_PING_TIMEOUTS, old_max_ping_timeouts)
194
- end
195
- end
196
- end
197
-
198
- describe "when validating a target" do
199
60
  before(:each) do
200
- @timer = flexmock("timer")
201
- flexmock(EM::Timer).should_receive(:new).and_return(@timer)
202
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
203
- @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
204
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker).by_default
205
- @agent.should_receive(:options).and_return({}).by_default
206
- RightScale::Sender.new(@agent)
207
- @instance = RightScale::Sender.instance
208
- @instance.initialize_offline_queue
209
- end
210
-
211
- it "should accept nil target" do
212
- @instance.__send__(:validate_target, nil, true).should be_true
213
- end
214
-
215
- it "should accept named target" do
216
- @instance.__send__(:validate_target, "name", true).should be_true
61
+ @log = flexmock(RightScale::Log)
62
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
63
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
64
+ @sender = create_sender(:http)
65
+ @token = "token"
66
+ @type = "/foo/bar"
67
+ @action = "bar"
68
+ @payload = {:pay => "load"}
69
+ @target = "target"
70
+ @callback = lambda { |_| }
71
+ end
72
+
73
+ context :initialize do
74
+ it "creates connectivity checker if mode is amqp" do
75
+ @sender = create_sender(:amqp)
76
+ @sender.connectivity_checker.should be_a(RightScale::ConnectivityChecker)
77
+ end
217
78
  end
218
79
 
219
- describe "and target is a hash" do
220
-
221
- describe "and selector is allowed" do
80
+ context "offline handling" do
222
81
 
223
- it "should accept :all or :any selector" do
224
- @instance.__send__(:validate_target, {:selector => :all}, true).should be_true
225
- @instance.__send__(:validate_target, {"selector" => "any"}, true).should be_true
82
+ context :initialize_offline_queue do
83
+ it "initializes offline handler" do
84
+ @sender = create_sender(:amqp, :offline_queueing => true)
85
+ flexmock(@sender.offline_handler).should_receive(:init).once
86
+ @sender.initialize_offline_queue
226
87
  end
227
88
 
228
- it "should reject values other than :all or :any" do
229
- lambda { @instance.__send__(:validate_target, {:selector => :other}, true) }.
230
- should raise_error(ArgumentError, /Invalid target selector/)
89
+ it "does nothing if offline queueing is disabled" do
90
+ flexmock(@sender.offline_handler).should_receive(:init).never
91
+ @sender.initialize_offline_queue
231
92
  end
232
-
233
93
  end
234
94
 
235
- describe "and selector is not allowed" do
236
-
237
- it "should reject selector" do
238
- lambda { @instance.__send__(:validate_target, {:selector => :all}, false) }.
239
- should raise_error(ArgumentError, /Invalid target hash/)
95
+ context :start_offline_queue do
96
+ it "starts offline handler" do
97
+ @sender = create_sender(:amqp, :offline_queueing => true)
98
+ flexmock(@sender.offline_handler).should_receive(:start).once
99
+ @sender.start_offline_queue
240
100
  end
241
101
 
102
+ it "does nothing if offline queueing is disabled" do
103
+ flexmock(@sender.offline_handler).should_receive(:start).never
104
+ @sender.start_offline_queue
105
+ end
242
106
  end
243
107
 
244
- describe "and tags is specified" do
245
-
246
- it "should accept tags" do
247
- @instance.__send__(:validate_target, {:tags => []}, true).should be_true
248
- @instance.__send__(:validate_target, {"tags" => ["tag"]}, true).should be_true
108
+ context :enable_offline_mode do
109
+ it "enables offline handler" do
110
+ @sender = create_sender(:amqp, :offline_queueing => true)
111
+ flexmock(@sender.offline_handler).should_receive(:enable).once
112
+ @sender.enable_offline_mode
249
113
  end
250
114
 
251
- it "should reject non-array" do
252
- lambda { @instance.__send__(:validate_target, {:tags => {}}, true) }.
253
- should raise_error(ArgumentError, /Invalid target tags/)
115
+ it "does nothing if offline queueing is disabled" do
116
+ flexmock(@sender.offline_handler).should_receive(:enable).never
117
+ @sender.enable_offline_mode
254
118
  end
255
-
256
119
  end
257
120
 
258
- describe "and scope is specified" do
259
-
260
- it "should accept account" do
261
- @instance.__send__(:validate_target, {:scope => {:account => 1}}, true).should be_true
262
- @instance.__send__(:validate_target, {"scope" => {"account" => 1}}, true).should be_true
121
+ context :disable_offline_mode do
122
+ it "initializes offline handler" do
123
+ @sender = create_sender(:amqp, :offline_queueing => true)
124
+ flexmock(@sender.offline_handler).should_receive(:disable).once
125
+ @sender.disable_offline_mode
263
126
  end
264
127
 
265
- it "should accept shard" do
266
- @instance.__send__(:validate_target, {:scope => {:shard => 1}}, true).should be_true
267
- @instance.__send__(:validate_target, {"scope" => {"shard" => 1}}, true).should be_true
128
+ it "does nothing if offline queueing is disabled" do
129
+ flexmock(@sender.offline_handler).should_receive(:disable).never
130
+ @sender.disable_offline_mode
268
131
  end
132
+ end
133
+ end
269
134
 
270
- it "should accept account and shard" do
271
- @instance.__send__(:validate_target, {"scope" => {:shard => 1, "account" => 1}}, true).should be_true
272
- end
135
+ context :send_push do
136
+ it "creates Push object" do
137
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
138
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.class == RightScale::Push &&
139
+ a.type == @type && a.from == @agent_id && a.target.nil? && a.persistent == true && a.confirm.nil? &&
140
+ a.token == "random token" && a.expires_at == 0 }, Time, nil).once
141
+ @sender.send_push(@type).should be_true
142
+ end
273
143
 
274
- it "should reject keys other than account and shard" do
275
- target = {"scope" => {:shard => 1, "account" => 1, :other => 2}}
276
- lambda { @instance.__send__(:validate_target, target, true) }.
277
- should raise_error(ArgumentError, /Invalid target scope/)
278
- end
144
+ it "stores payload" do
145
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.payload == @payload }, Time, nil).once
146
+ @sender.send_push(@type, @payload).should be_true
147
+ end
279
148
 
280
- it "should reject empty hash" do
281
- lambda { @instance.__send__(:validate_target, {:scope => {}}, true) }.
282
- should raise_error(ArgumentError, /Invalid target scope/)
283
- end
149
+ it "sets specific target using string" do
150
+ flexmock(@sender).should_receive(:http_send).with(:send_push, @target, on { |a| a.target == @target &&
151
+ a.selector == :any }, Time, nil).once
152
+ @sender.send_push(@type, nil, @target).should be_true
153
+ end
284
154
 
155
+ it "sets specific target using :agent_id" do
156
+ target = {:agent_id => @agent_id}
157
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.target == @agent_id }, Time, nil).once
158
+ @sender.send_push(@type, nil, target).should be_true
285
159
  end
286
160
 
287
- describe "and multiple are specified" do
161
+ it "sets target tags" do
162
+ tags = ["a:b=c"]
163
+ target = {:tags => tags}
164
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.tags == tags &&
165
+ a.selector == :any }, Time, nil).once
166
+ @sender.send_push(@type, nil, target).should be_true
167
+ end
288
168
 
289
- it "should accept scope and tags" do
290
- @instance.__send__(:validate_target, {:scope => {:shard => 1}, :tags => []}, true).should be_true
291
- end
169
+ it "sets target scope" do
170
+ scope = {:shard => 1, :account => 123}
171
+ target = {:scope => scope}
172
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.scope == scope &&
173
+ a.selector == :any }, Time, nil).once
174
+ @sender.send_push(@type, nil, target).should be_true
175
+ end
292
176
 
293
- it "should accept scope, tags, and selector" do
294
- target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
295
- @instance.__send__(:validate_target, target, true).should be_true
296
- end
177
+ it "sets target selector" do
178
+ tags = ["a:b=c"]
179
+ target = {:tags => tags, :selector => :all}
180
+ flexmock(@sender).should_receive(:http_send).with(:send_push, target, on { |a| a.selector == :all }, Time, nil).once
181
+ @sender.send_push(@type, nil, target).should be_true
182
+ end
297
183
 
298
- it "should reject selector if not allowed" do
299
- target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
300
- lambda { @instance.__send__(:validate_target, target, false) }.
301
- should raise_error(ArgumentError, /Invalid target hash/)
302
- end
184
+ it "sets token" do
185
+ flexmock(@sender).should_receive(:http_send).with(:send_push, @target, on { |a| a.token == "token2" }, Time, nil).once
186
+ @sender.send_push(@type, nil, @target, "token2").should be_true
187
+ end
303
188
 
189
+ it "sets applies callback for returning response" do
190
+ flexmock(@sender).should_receive(:http_send).with(:send_push, nil, on { |a| a.confirm == true }, Time, Proc).once
191
+ @sender.send_push(@type) { |_| }.should be_true
304
192
  end
193
+ end
305
194
 
306
- it "should reject keys other than selector, scope, and tags" do
307
- target = {:scope => {:shard => 1}, :tags => [], :selector => :all, :other => 2}
308
- lambda { @instance.__send__(:validate_target, target, true) }.
309
- should raise_error(ArgumentError, /Invalid target hash/)
195
+ context :send_request do
196
+ it "creates Request object" do
197
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
198
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.class == RightScale::Request &&
199
+ a.type == @type && a.from == @agent_id && a.target.nil? && a.persistent.nil? && a.selector == :any &&
200
+ a.token == "random token" && a.expires_at == 0 }, Time, Proc).once
201
+ @sender.send_request(@type) { |_| }.should be_true
310
202
  end
311
203
 
312
- it "should reject empty hash" do
313
- lambda { @instance.__send__(:validate_target, {}, true) }.
314
- should raise_error(ArgumentError, /Invalid target hash/)
204
+ it "stores payload" do
205
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.payload == @payload }, Time, Proc)
206
+ @sender.send_request(@type, @payload) { |_| }.should be_true
315
207
  end
316
208
 
317
- it "should reject value that is not nil, string, or hash" do
318
- lambda { @instance.__send__(:validate_target, [], true) }.
319
- should raise_error(ArgumentError, /Invalid target/)
209
+ it "sets specific target using string" do
210
+ flexmock(@sender).should_receive(:http_send).with(:send_request, @agent_id, on { |a| a.target == @agent_id &&
211
+ a.selector == :any }, Time, Proc).once
212
+ @sender.send_request(@type, nil, @agent_id) { |_| }.should be_true
320
213
  end
321
214
 
322
- end
215
+ it "sets specific target using :agent_id" do
216
+ target = {:agent_id => @agent_id}
217
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.target == @agent_id }, Time, Proc).once
218
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
219
+ end
323
220
 
324
- end
221
+ it "sets target tags" do
222
+ tags = ["a:b=c"]
223
+ target = {:tags => tags}
224
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.tags == tags &&
225
+ a.selector == :any }, Time, Proc).once
226
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
227
+ end
325
228
 
326
- describe "when making a push request" do
327
- before(:each) do
328
- @timer = flexmock("timer")
329
- flexmock(EM::Timer).should_receive(:new).and_return(@timer)
330
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
331
- @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
332
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker).by_default
333
- @agent.should_receive(:options).and_return({}).by_default
334
- RightScale::Sender.new(@agent)
335
- @instance = RightScale::Sender.instance
336
- @instance.initialize_offline_queue
337
- end
229
+ it "sets target scope" do
230
+ scope = {:shard => 1, :account => 123}
231
+ target = {:scope => scope}
232
+ flexmock(@sender).should_receive(:http_send).with(:send_request, target, on { |a| a.scope == scope &&
233
+ a.selector == :any }, Time, Proc).once
234
+ @sender.send_request(@type, nil, target) { |_| }.should be_true
235
+ end
338
236
 
339
- it "should validate target" do
340
- @broker.should_receive(:publish)
341
- flexmock(@instance).should_receive(:validate_target).with("target", true).once
342
- @instance.send_push('/foo/bar', nil, "target").should be_true
343
- end
237
+ it "sets token" do
238
+ flexmock(@sender).should_receive(:http_send).with(:send_request, @target, on { |a| a.token == "token2" }, Time, Proc).once
239
+ @sender.send_request(@type, nil, @target, "token2") { |_| }.should be_true
240
+ end
344
241
 
345
- it "should create a Push object" do
346
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
347
- push.class.should == RightScale::Push
348
- end, hsh(:persistent => false, :mandatory => true)).once
349
- @instance.send_push('/welcome/aboard', 'iZac')
350
- end
242
+ it "sets expiration time if TTL enabled" do
243
+ @sender = create_sender(:http, :time_to_live => 60)
244
+ flexmock(@sender).should_receive(:http_send).with(:send_request, nil, on { |a| a.expires_at != 0 }, Time, Proc).once
245
+ @sender.send_request(@type) { |_| }.should be_true
246
+ end
351
247
 
352
- it "should set the correct target if specified" do
353
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
354
- push.target.should == 'my-target'
355
- end, hsh(:persistent => false, :mandatory => true)).once
356
- @instance.send_push('/welcome/aboard', 'iZac', 'my-target')
357
- end
248
+ it "does not allow fanout" do
249
+ lambda { @sender.send_request(@type, nil, :selector => :all) }.should raise_error(ArgumentError)
250
+ end
358
251
 
359
- it "should set the correct target selectors for fanout if specified" do
360
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
361
- push.tags.should == ['tag']
362
- push.selector.should == :all
363
- push.scope.should == {:account => 123}
364
- end, hsh(:persistent => false, :mandatory => true)).once
365
- @instance.send_push('/welcome/aboard', 'iZac', :tags => ['tag'], :selector => :all, :scope => {:account => 123})
252
+ it "requires callback block" do
253
+ lambda { @sender.send_request(@type) }.should raise_error(ArgumentError)
254
+ end
366
255
  end
367
256
 
368
- it "should default the target selector to any" do
369
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
370
- push.tags.should == ['tag']
371
- push.scope.should == {:account => 123}
372
- end, hsh(:persistent => false, :mandatory => true)).once
373
- @instance.send_push('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
374
- end
257
+ context :build_and_send_packet do
258
+ [:http, :amqp].each do |mode|
375
259
 
376
- it "should set correct attributes on the push message" do
377
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
378
- push.type.should == '/welcome/aboard'
379
- push.token.should_not be_nil
380
- push.persistent.should be_false
381
- push.from.should == 'agent'
382
- push.target.should be_nil
383
- push.confirm.should be_nil
384
- push.expires_at.should == 0
385
- end, hsh(:persistent => false, :mandatory => true)).once
386
- @instance.send_push('/welcome/aboard', 'iZac')
387
- end
260
+ context "when #{mode}" do
261
+ before(:each) do
262
+ @sender = create_sender(mode)
263
+ end
388
264
 
389
- it 'should queue the push if in offline mode and :offline_queueing enabled' do
390
- @agent.should_receive(:options).and_return({:offline_queueing => true})
391
- RightScale::Sender.new(@agent)
392
- @instance = RightScale::Sender.instance
393
- @instance.initialize_offline_queue
394
- @broker.should_receive(:publish).never
395
- @instance.enable_offline_mode
396
- @instance.offline_handler.mode.should == :offline
397
- @instance.send_push('/welcome/aboard', 'iZac')
398
- @instance.offline_handler.queue.size.should == 1
399
- end
265
+ it "builds packet" do
266
+ packet = flexmock("packet", :type => @type, :token => @token, :selector => :any)
267
+ flexmock(@sender).should_receive(:build_packet).with(:send_push, @type, @payload, @target, @token, nil).and_return(packet).once
268
+ flexmock(@sender).should_receive("#{mode}_send".to_sym)
269
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
270
+ end
400
271
 
401
- it 'should raise exception if not connected to any brokers and :offline_queueing disabled' do
402
- @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
403
- @broker.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers)
404
- @agent.should_receive(:options).and_return({:offline_queueing => false})
405
- RightScale::Sender.new(@agent)
406
- @instance = RightScale::Sender.instance
407
- lambda { @instance.send_push('/welcome/aboard', 'iZac') }.should raise_error(RightScale::Sender::TemporarilyOffline)
408
- end
272
+ it "sends packet" do
273
+ flexmock(@sender).should_receive("#{mode}_send".to_sym).with(:send_push, @target,
274
+ on { |a| a.class == RightScale::Push }, Time, nil).once
275
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
276
+ end
409
277
 
410
- it "should store the response handler if given" do
411
- response_handler = lambda {}
412
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
413
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
414
- push.confirm.should == true
415
- end, hsh(:persistent => false, :mandatory => true)).once
416
- @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
417
- @instance.pending_requests['abc'].response_handler.should == response_handler
278
+ it "ignores nil packet result when queueing" do
279
+ flexmock(@sender).should_receive(:build_packet).with(:send_push, @type, @payload, @target, @token, nil).and_return(nil).once
280
+ flexmock(@sender).should_receive("#{mode}_send".to_sym).never
281
+ @sender.build_and_send_packet(:send_push, @type, @payload, @target, @token, callback = nil).should be_true
282
+ end
283
+ end
284
+ end
418
285
  end
419
286
 
420
- it "should store the request receive time if there is a response handler" do
421
- response_handler = lambda {}
422
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
423
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::PUSH_KINDS).youngest_age.should be_nil
424
- @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
425
- @instance.pending_requests['abc'].receive_time.should == Time.at(1000000)
426
- flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
427
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::PUSH_KINDS).youngest_age.should == 100
428
- end
287
+ context :build_packet do
288
+ [:send_push, :send_request].each do |kind|
429
289
 
430
- it "should eventually remove push from pending requests if no response received" do
431
- response_handler = lambda {}
432
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc', 'xyz').twice
433
- @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
434
- @instance.pending_requests['abc'].should_not be_nil
435
- flexmock(Time).should_receive(:now).and_return(Time.at(1000121))
436
- @instance.send_push('/welcome/aboard', 'iZac', &response_handler)
437
- @instance.pending_requests['xyz'].should_not be_nil
438
- @instance.pending_requests['abc'].should be_nil
439
- end
290
+ context "when #{kind}" do
291
+ it "validates target" do
292
+ flexmock(@sender).should_receive(:validate_target).with(@target, kind == :send_push).once
293
+ @sender.build_packet(kind, @type, nil, @target, @token).should_not be_nil
294
+ end
440
295
 
441
- it "should log exceptions and re-raise them" do
442
- @log.should_receive(:error).with(/Failed to publish request/, Exception, :trace).once
443
- @broker.should_receive(:publish).and_raise(Exception)
444
- lambda { @instance.send_push('/welcome/aboard', 'iZac') }.should raise_error(RightScale::Sender::SendFailure)
445
- end
446
- end
296
+ it "submits request to offline handler and returns nil if queueing" do
297
+ flexmock(@sender).should_receive(:queueing?).and_return(true).once
298
+ @sender.build_packet(kind, @type, nil, @target, @token).should be_nil
299
+ end
447
300
 
448
- describe "when making a send_persistent_push request" do
449
- before(:each) do
450
- @timer = flexmock("timer")
451
- flexmock(EM::Timer).should_receive(:new).and_return(@timer)
452
- @broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
453
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {}).by_default
454
- RightScale::Sender.new(@agent)
455
- @instance = RightScale::Sender.instance
456
- @instance.initialize_offline_queue
457
- end
301
+ it "generates a request token if none provided" do
302
+ flexmock(RightSupport::Data::UUID, :generate => "random token")
303
+ packet = @sender.build_packet(kind, @type, nil, @target, token = nil)
304
+ packet.token.should == "random token"
305
+ end
458
306
 
459
- it "should create a Push object" do
460
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
461
- push.class.should == RightScale::Push
462
- end, hsh(:persistent => true, :mandatory => true)).once
463
- @instance.send_persistent_push('/welcome/aboard', 'iZac')
464
- end
307
+ it "sets payload" do
308
+ packet = @sender.build_packet(kind, @type, @payload, @target, @token)
309
+ packet.payload.should == @payload
310
+ end
465
311
 
466
- it "should set correct attributes on the push message" do
467
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
468
- push.type.should == '/welcome/aboard'
469
- push.token.should_not be_nil
470
- push.persistent.should be_true
471
- push.from.should == 'agent'
472
- push.target.should be_nil
473
- push.confirm.should be_nil
474
- push.expires_at.should == 0
475
- end, hsh(:persistent => true, :mandatory => true)).once
476
- @instance.send_persistent_push('/welcome/aboard', 'iZac')
477
- end
312
+ it "sets the packet from this agent" do
313
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
314
+ packet.from.should == @agent_id
315
+ end
478
316
 
479
- it "should default the target selector to any" do
480
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
481
- push.tags.should == ['tag']
482
- push.scope.should == {:account => 123}
483
- end, hsh(:persistent => true, :mandatory => true)).once
484
- @instance.send_persistent_push('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123})
485
- end
317
+ it "sets target if target is not a hash" do
318
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
319
+ packet.target.should == @target
320
+ end
486
321
 
487
- it "should store the response handler if given" do
488
- response_handler = lambda {}
489
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
490
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |push|
491
- push.confirm.should == true
492
- end, hsh(:persistent => true, :mandatory => true)).once
493
- @instance.send_persistent_push('/welcome/aboard', 'iZac', &response_handler)
494
- @instance.pending_requests['abc'].response_handler.should == response_handler
495
- end
322
+ context "when target is a hash" do
323
+ it "sets agent ID" do
324
+ target = {:agent_id => @agent_id}
325
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
326
+ packet.target.should == @agent_id
327
+ end
328
+
329
+ it "sets tags" do
330
+ tags = ["a:b=c"]
331
+ target = {:tags => tags}
332
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
333
+ packet.tags.should == tags
334
+ packet.scope.should be_nil
335
+ end
336
+
337
+ it "sets scope" do
338
+ scope = {:shard => 1, :account => 123}
339
+ target = {:scope => scope}
340
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
341
+ packet.tags.should == []
342
+ end
343
+ end
496
344
 
497
- it "should store the request receive time if there is a response handler" do
498
- response_handler = lambda {}
499
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
500
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
501
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::PUSH_KINDS).youngest_age.should be_nil
502
- @instance.send_persistent_push('/welcome/aboard', 'iZac', &response_handler)
503
- @instance.pending_requests['abc'].receive_time.should == Time.at(1000000)
504
- flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
505
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::PUSH_KINDS).youngest_age.should == 100
345
+ if kind == :send_push
346
+ it "defaults selector to :any" do
347
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
348
+ packet.selector.should == :any
349
+ end
350
+
351
+ it "sets selector" do
352
+ target = {:selector => :all}
353
+ packet = @sender.build_packet(kind, @type, nil, target, @token)
354
+ packet.selector.should == :all
355
+ end
356
+
357
+ it "sets persistent flag" do
358
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
359
+ packet.persistent.should be_true
360
+ end
361
+
362
+ it "enables confirm if callback provided" do
363
+ packet = @sender.build_packet(kind, @type, nil, @target, @token, @callback)
364
+ packet.confirm.should be_true
365
+ end
366
+
367
+ it "does not enable confirm if not callback provided" do
368
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
369
+ packet.confirm.should be_false
370
+ end
371
+
372
+ it "does not set expiration time" do
373
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
374
+ packet.expires_at.should == 0
375
+ end
376
+ else
377
+ it "always sets selector to :any" do
378
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
379
+ packet.selector.should == :any
380
+ end
381
+
382
+ it "sets expiration time to specified TTL" do
383
+ @sender = create_sender(:http, :time_to_live => 99)
384
+ now = Time.now
385
+ flexmock(Time).should_receive(:now).and_return(now)
386
+ packet = @sender.build_packet(kind, @type, nil, @target, @token)
387
+ packet.expires_at.should == (now + 99).to_i
388
+ end
389
+ end
390
+ end
391
+ end
506
392
  end
507
- end
508
393
 
509
- describe "when making a send_retryable_request request" do
510
- before(:each) do
511
- @timer = flexmock("timer")
512
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
513
- @broker_id = "rs-broker-host-123"
514
- @broker_ids = [@broker_id]
515
- @broker = flexmock("Broker", :subscribe => true, :publish => @broker_ids, :connected? => true,
516
- :all => @broker_ids, :identity_parts => ["host", 123, 0, 0]).by_default
517
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker).by_default
518
- @agent.should_receive(:options).and_return({:ping_interval => 0, :time_to_live => 100}).by_default
519
- RightScale::Sender.new(@agent)
520
- @instance = RightScale::Sender.instance
521
- @instance.initialize_offline_queue
522
- end
394
+ context :handle_response do
395
+ before(:each) do
396
+ flexmock(RightSupport::Data::UUID, :generate => @token)
397
+ @received_at = Time.now
398
+ flexmock(Time).should_receive(:now).and_return(@received_at)
399
+ @callback = lambda { |_| }
400
+ @pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
401
+ @sender.pending_requests[@token] = @pending_request
402
+ end
523
403
 
524
- it "should validate target" do
525
- @broker.should_receive(:publish).and_return(@broker_ids).once
526
- flexmock(@instance).should_receive(:validate_target).with("target", false).once
527
- @instance.send_retryable_request('/foo/bar', nil, "target") {_}.should be_true
528
- end
404
+ [:send_push, :send_request].each do |kind|
405
+ it "delivers the response for a #{kind}" do
406
+ @pending_request = RightScale::PendingRequest.new(kind, @received_at, @callback)
407
+ @sender.pending_requests[@token] = @pending_request
408
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
409
+ flexmock(@sender).should_receive(:deliver_response).with(response, @pending_request).once
410
+ @sender.handle_response(response).should be_true
411
+ end
412
+ end
529
413
 
530
- it "should create a Request object" do
531
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
532
- request.class.should == RightScale::Request
533
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).once
534
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
535
- end
414
+ it "logs a debug message if request no longer pending" do
415
+ @sender.pending_requests.delete(@token)
416
+ @log.should_receive(:debug).with(/No pending request for response/).once
417
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
418
+ @sender.handle_response(response)
419
+ end
536
420
 
537
- it "should set correct attributes on the request message" do
538
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
539
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
540
- request.type.should == '/welcome/aboard'
541
- request.token.should_not be_nil
542
- request.persistent.should be_false
543
- request.from.should == 'agent'
544
- request.target.should be_nil
545
- request.expires_at.should == 1000100
546
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).once
547
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
548
- end
421
+ it "ignores responses that are not a Result" do
422
+ flexmock(@sender).should_receive(:deliver_response).never
423
+ @sender.handle_response("response").should be_true
424
+ end
549
425
 
550
- it "should disable time-to-live if disabled in configuration" do
551
- @agent.should_receive(:options).and_return({:ping_interval => 0, :time_to_live => 0})
552
- RightScale::Sender.new(@agent)
553
- @instance = RightScale::Sender.instance
554
- @instance.initialize_offline_queue
555
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
556
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
557
- request.expires_at.should == 0
558
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).once
559
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
560
- end
426
+ context "when non-delivery" do
427
+ before(:each) do
428
+ @reason = RightScale::OperationResult::TARGET_NOT_CONNECTED
429
+ non_delivery = RightScale::OperationResult.non_delivery(@reason)
430
+ @response = RightScale::Result.new(@token, "to", non_delivery, @target)
431
+ end
561
432
 
562
- it "should set the correct target if specified" do
563
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
564
- request.target.should == 'my-target'
565
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).once
566
- @instance.send_retryable_request('/welcome/aboard', 'iZac', 'my-target') {|_|}
567
- end
433
+ it "records non-delivery regardless of whether there is a pending request" do
434
+ @sender.pending_requests.delete(@token)
435
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
436
+ response = RightScale::Result.new(@token, "to", non_delivery, @target)
437
+ @sender.handle_response(response).should be_true
438
+ @sender.instance_variable_get(:@non_delivery_stats).total.should == 1
439
+ end
568
440
 
569
- it "should set the correct target selectors if specified" do
570
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
571
- request.tags.should == ['tag']
572
- request.selector.should == :any
573
- request.scope.should == {:account => 123}
574
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).once
575
- @instance.send_retryable_request('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123}) {|_|}
576
- end
441
+ it "logs non-delivery if there is no pending request" do
442
+ @sender.pending_requests.delete(@token)
443
+ @log.should_receive(:info).with(/Non-delivery of/).once
444
+ non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
445
+ response = RightScale::Result.new(@token, "to", non_delivery, @target)
446
+ @sender.handle_response(response).should be_true
447
+ end
577
448
 
578
- it "should set up for retrying the request if necessary by default" do
579
- flexmock(@instance).should_receive(:publish_with_timeout_retry).once
580
- @instance.send_retryable_request('/welcome/aboard', 'iZac', 'my-target') {|_|}
581
- end
449
+ context "for a Request" do
450
+
451
+ context "with target not connected" do
452
+ it "logs non-delivery but does not deliver it" do
453
+ @log.should_receive(:info).with(/Non-delivery of/).once
454
+ flexmock(@sender).should_receive(:deliver_response).never
455
+ @sender.handle_response(@response).should be_true
456
+ end
457
+
458
+ it "records non-delivery reason in pending request if for parent request" do
459
+ @log.should_receive(:info).with(/Non-delivery of/).once
460
+ flexmock(@sender).should_receive(:deliver_response).never
461
+ parent_token = "parent token"
462
+ parent_pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
463
+ @sender.pending_requests[parent_token] = parent_pending_request
464
+ @pending_request.retry_parent_token = parent_token
465
+ @sender.handle_response(@response).should be_true
466
+ parent_pending_request.non_delivery.should == @reason
467
+ end
468
+
469
+ it "updates non-delivery reason if for retry request" do
470
+ flexmock(@sender).should_receive(:deliver_response).never
471
+ @sender.handle_response(@response).should be_true
472
+ @pending_request.non_delivery.should == @reason
473
+ end
474
+ end
582
475
 
583
- it "should store the response handler" do
584
- response_handler = lambda {}
585
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
586
- @instance.send_retryable_request('/welcome/aboard', 'iZac', &response_handler)
587
- @instance.pending_requests['abc'].response_handler.should == response_handler
588
- end
476
+ context "with retry timeout and previous non-delivery" do
477
+ it "delivers response using stored non-delivery reason" do
478
+ @reason = RightScale::OperationResult::RETRY_TIMEOUT
479
+ @response.results = RightScale::OperationResult.non_delivery(@reason)
480
+ @pending_request.non_delivery = "other reason"
481
+ flexmock(@sender).should_receive(:deliver_response).with(on { |a| a.results.content == "other reason"},
482
+ @pending_request).once
483
+ @sender.handle_response(@response).should be_true
484
+ end
485
+ end
589
486
 
590
- it "should store the request receive time" do
591
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
592
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
593
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::REQUEST_KINDS).youngest_age.should be_nil
594
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
595
- @instance.pending_requests['abc'].receive_time.should == Time.at(1000000)
596
- flexmock(Time).should_receive(:now).and_return(Time.at(1000100))
597
- @instance.pending_requests.kind(RightScale::Sender::PendingRequests::REQUEST_KINDS).youngest_age.should == 100
487
+ context "otherwise" do
488
+ it "delivers non-delivery response as is" do
489
+ @response.results = RightScale::OperationResult.non_delivery("other")
490
+ flexmock(@sender).should_receive(:deliver_response).with(on { |a| a.results.content == "other"},
491
+ RightScale::PendingRequest).once
492
+ @sender.handle_response(@response).should be_true
493
+ end
494
+ end
495
+ end
496
+ end
598
497
  end
599
498
 
600
- it 'should queue the request if in offline mode and :offline_queueing enabled' do
601
- @agent.should_receive(:options).and_return({:offline_queueing => true})
602
- RightScale::Sender.new(@agent)
603
- @instance = RightScale::Sender.instance
604
- @instance.initialize_offline_queue
605
- @broker.should_receive(:publish).never
606
- @instance.enable_offline_mode
607
- @instance.offline_handler.mode.should == :offline
608
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
609
- @instance.offline_handler.queue.size.should == 1
610
- end
499
+ context :terminate do
500
+ it "terminates offline handler" do
501
+ flexmock(@sender.offline_handler).should_receive(:terminate).once
502
+ @sender.terminate
503
+ end
611
504
 
612
- it 'should raise exception if not connected to any brokers and :offline_queueing disabled' do
613
- @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
614
- @broker.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers)
615
- @agent.should_receive(:options).and_return({:offline_queueing => false})
616
- RightScale::Sender.new(@agent)
617
- @instance = RightScale::Sender.instance
618
- lambda { @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|} }.should raise_error(RightScale::Sender::TemporarilyOffline)
619
- end
505
+ it "terminates connectivity checker if configured" do
506
+ @sender = create_sender(:amqp, :offline_queueing => true)
507
+ flexmock(@sender.connectivity_checker).should_receive(:terminate).once
508
+ @sender.terminate
509
+ end
620
510
 
621
- it "should dump the pending requests" do
622
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
623
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
624
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
625
- @instance.dump_requests.should == ["#{Time.at(1000000).localtime} <abc>"]
511
+ it "returns number of pending requests and age of youngest request" do
512
+ receive_time = Time.now - 10
513
+ @sender.pending_requests[@token] = RightScale::PendingRequest.new(:send_request, receive_time, @callback)
514
+ @sender.terminate.should == [1, 10]
515
+ end
626
516
  end
627
517
 
628
- it "should not allow a selector target" do
629
- lambda { @instance.send_retryable_request('/welcome/aboard', 'iZac', :selector => :all) }.should raise_error(ArgumentError)
630
- end
518
+ context :dump_requests do
519
+ it "returns array of unfinished non-push requests" do
520
+ time1 = Time.now
521
+ @sender.pending_requests["token1"] = RightScale::PendingRequest.new(:send_push, time1, @callback)
522
+ time2 = time1 + 10
523
+ @sender.pending_requests["token2"] = RightScale::PendingRequest.new(:send_request, time2, @callback)
524
+ @sender.dump_requests.should == ["#{time2.localtime} <token2>"]
525
+ end
631
526
 
632
- it "should raise error if there is no callback block" do
633
- lambda { @instance.send_retryable_request('/welcome/aboard', 'iZac') }.should raise_error(ArgumentError)
634
- end
527
+ it "returns requests in descending time order" do
528
+ time1 = Time.now
529
+ @sender.pending_requests["token1"] = RightScale::PendingRequest.new(:send_request, time1, @callback)
530
+ time2 = time1 + 10
531
+ @sender.pending_requests["token2"] = RightScale::PendingRequest.new(:send_request, time2, @callback)
532
+ @sender.dump_requests.should == ["#{time2.localtime} <token2>", "#{time1.localtime} <token1>"]
533
+ end
635
534
 
636
- it "should log exceptions and re-raise them" do
637
- @log.should_receive(:error).with(/Failed to publish request/, Exception, :trace).once
638
- @broker.should_receive(:publish).and_raise(Exception)
639
- lambda { @instance.send_retryable_request('/welcome/aboard', 'iZac') {|r|} }.should raise_error(RightScale::Sender::SendFailure)
535
+ it "limits the number returned to 50" do
536
+ pending_request = RightScale::PendingRequest.new(:send_request, Time.now, @callback)
537
+ 55.times.each { |i| @sender.pending_requests["token#{i}"] = pending_request }
538
+ result = @sender.dump_requests
539
+ result.size.should == 51
540
+ result.last.should == "..."
541
+ end
640
542
  end
641
543
 
642
- describe "with retry" do
643
- it "should not setup for retry if retry_timeout nil" do
644
- flexmock(EM).should_receive(:add_timer).never
645
- @agent.should_receive(:options).and_return({:retry_timeout => nil})
646
- RightScale::Sender.new(@agent)
647
- @instance = RightScale::Sender.instance
648
- @broker.should_receive(:publish).and_return(@broker_ids).once
649
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
544
+ context :validate_target do
545
+ it "should accept nil target" do
546
+ @sender.send(:validate_target, nil, true).should be_true
650
547
  end
651
548
 
652
- it "should not setup for retry if retry_interval nil" do
653
- flexmock(EM).should_receive(:add_timer).never
654
- @agent.should_receive(:options).and_return({:retry_interval => nil})
655
- RightScale::Sender.new(@agent)
656
- @instance = RightScale::Sender.instance
657
- @broker.should_receive(:publish).and_return(@broker_ids).once
658
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
549
+ it "should accept named target" do
550
+ @sender.send(:validate_target, "name", true).should be_true
659
551
  end
660
552
 
661
- it "should not setup for retry if publish failed" do
662
- flexmock(EM).should_receive(:add_timer).never
663
- @agent.should_receive(:options).and_return({:retry_timeout => 60, :retry_interval => 60})
664
- RightScale::Sender.new(@agent)
665
- @instance = RightScale::Sender.instance
666
- @broker.should_receive(:publish).and_raise(Exception).once
667
- @log.should_receive(:error).with(/Failed to publish request/, Exception, :trace).once
668
- lambda do
669
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
670
- end.should raise_error(RightScale::Sender::SendFailure)
671
- end
553
+ context "when target is a hash" do
672
554
 
673
- it "should setup for retry if retry_timeout and retry_interval not nil and publish successful" do
674
- flexmock(EM).should_receive(:add_timer).with(60, any).once
675
- @agent.should_receive(:options).and_return({:retry_timeout => 60, :retry_interval => 60})
676
- RightScale::Sender.new(@agent)
677
- @instance = RightScale::Sender.instance
678
- @broker.should_receive(:publish).and_return(@broker_ids).once
679
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
680
- end
555
+ context "and agent ID is specified" do
556
+ it "should not allow other keys" do
557
+ @sender.send(:validate_target, {:agent_id => @agent_id}, true).should be_true
558
+ lambda { @sender.send(:validate_target, {:agent_id => @agent_id, :tags => ["a:b=c"]}, true) }.should \
559
+ raise_error(ArgumentError, /Invalid target/)
560
+ end
561
+ end
681
562
 
682
- it "should succeed after retrying once" do
683
- EM.run do
684
- token = 'abc'
685
- result = RightScale::OperationResult.non_delivery(RightScale::OperationResult::RETRY_TIMEOUT)
686
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
687
- @agent.should_receive(:options).and_return({:retry_timeout => 0.3, :retry_interval => 0.1})
688
- RightScale::Sender.new(@agent)
689
- @instance = RightScale::Sender.instance
690
- flexmock(@instance.connectivity_checker).should_receive(:check).once
691
- @broker.should_receive(:publish).and_return(@broker_ids).twice
692
- @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
693
- result = RightScale::OperationResult.from_results(response)
563
+ context "and selector is allowed" do
564
+ it "should accept :all or :any selector" do
565
+ @sender.send(:validate_target, {:selector => :all}, true).should be_true
566
+ @sender.send(:validate_target, {"selector" => "any"}, true).should be_true
694
567
  end
695
- EM.add_timer(0.15) do
696
- @instance.pending_requests.empty?.should be_false
697
- result = RightScale::Result.new(token, nil, {'from' => RightScale::OperationResult.success}, nil)
698
- @instance.handle_response(result)
568
+
569
+ it "should reject values other than :all or :any" do
570
+ lambda { @sender.send(:validate_target, {:selector => :other}, true) }.
571
+ should raise_error(ArgumentError, /Invalid target selector/)
699
572
  end
700
- EM.add_timer(0.3) do
701
- EM.stop
702
- result.success?.should be_true
703
- @instance.pending_requests.empty?.should be_true
573
+ end
574
+
575
+ context "and selector is not allowed" do
576
+ it "should reject selector" do
577
+ lambda { @sender.send(:validate_target, {:selector => :all}, false) }.
578
+ should raise_error(ArgumentError, /Invalid target hash/)
704
579
  end
705
580
  end
706
- end
707
581
 
708
- it "should respond with retry if publish fails with no brokers when retrying" do
709
- EM.run do
710
- token = 'abc'
711
- result = RightScale::OperationResult.non_delivery(RightScale::OperationResult::RETRY_TIMEOUT)
712
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
713
- @agent.should_receive(:options).and_return({:retry_timeout => 0.3, :retry_interval => 0.1})
714
- RightScale::Sender.new(@agent)
715
- @instance = RightScale::Sender.instance
716
- flexmock(@instance.connectivity_checker).should_receive(:check).never
717
- @broker.should_receive(:publish).and_return(@broker_ids).ordered.once
718
- @broker.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).ordered.once
719
- @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
720
- @log.should_receive(:error).with(/Failed retry for.*temporarily offline/).once
721
- @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
722
- result = RightScale::OperationResult.from_results(response)
723
- result.retry?.should be_true
724
- result.content.should == "lost connectivity"
582
+ context "and tags is specified" do
583
+ it "should accept tags" do
584
+ @sender.send(:validate_target, {:tags => []}, true).should be_true
585
+ @sender.send(:validate_target, {"tags" => ["tag"]}, true).should be_true
725
586
  end
726
- EM.add_timer(0.15) do
727
- EM.stop
728
- result.retry?.should be_true
729
- @instance.pending_requests.empty?.should be_true
587
+
588
+ it "should reject non-array" do
589
+ lambda { @sender.send(:validate_target, {:tags => {}}, true) }.
590
+ should raise_error(ArgumentError, /Invalid target tags/)
730
591
  end
731
592
  end
732
- end
733
593
 
734
- it "should respond with non-delivery if publish fails in unknown way when retrying" do
735
- EM.run do
736
- token = 'abc'
737
- result = RightScale::OperationResult.non_delivery(RightScale::OperationResult::RETRY_TIMEOUT)
738
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
739
- @agent.should_receive(:options).and_return({:retry_timeout => 0.3, :retry_interval => 0.1})
740
- RightScale::Sender.new(@agent)
741
- @instance = RightScale::Sender.instance
742
- flexmock(@instance.connectivity_checker).should_receive(:check).never
743
- @broker.should_receive(:publish).and_return(@broker_ids).ordered.once
744
- @broker.should_receive(:publish).and_raise(Exception.new).ordered.once
745
- @log.should_receive(:error).with(/Failed to publish request/, Exception, :trace).once
746
- @log.should_receive(:error).with(/Failed retry for.*send failure/).once
747
- @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
748
- result = RightScale::OperationResult.from_results(response)
749
- result.non_delivery?.should be_true
750
- result.content.should == "retry failed"
594
+ context "and scope is specified" do
595
+ it "should accept account" do
596
+ @sender.send(:validate_target, {:scope => {:account => 1}}, true).should be_true
597
+ @sender.send(:validate_target, {"scope" => {"account" => 1}}, true).should be_true
751
598
  end
752
- EM.add_timer(0.15) do
753
- EM.stop
754
- result.non_delivery?.should be_true
755
- @instance.pending_requests.empty?.should be_true
599
+
600
+ it "should accept shard" do
601
+ @sender.send(:validate_target, {:scope => {:shard => 1}}, true).should be_true
602
+ @sender.send(:validate_target, {"scope" => {"shard" => 1}}, true).should be_true
756
603
  end
757
- end
758
- end
759
604
 
760
- it "should not respond if retry mechanism fails in unknown way" do
761
- EM.run do
762
- token = 'abc'
763
- result = nil
764
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
765
- @agent.should_receive(:options).and_return({:retry_timeout => 0.3, :retry_interval => 0.1})
766
- RightScale::Sender.new(@agent)
767
- @instance = RightScale::Sender.instance
768
- flexmock(@instance.connectivity_checker).should_receive(:check).and_raise(Exception).once
769
- @broker.should_receive(:publish).and_return(@broker_ids).twice
770
- @log.should_receive(:error).with(/Failed retry for.*without responding/, Exception, :trace).once
771
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {_}
772
- EM.add_timer(0.15) do
773
- EM.stop
774
- result.should be_nil
775
- @instance.pending_requests.empty?.should be_false
605
+ it "should accept account and shard" do
606
+ @sender.send(:validate_target, {"scope" => {:shard => 1, "account" => 1}}, true).should be_true
776
607
  end
777
- end
778
- end
779
608
 
780
- it "should timeout after retrying twice" do
781
- pending 'Too difficult to get timing right for Windows' if RightScale::Platform.windows?
782
- EM.run do
783
- result = RightScale::OperationResult.success
784
- @log.should_receive(:warning).once
785
- @agent.should_receive(:options).and_return({:retry_timeout => 0.6, :retry_interval => 0.1})
786
- RightScale::Sender.new(@agent)
787
- @instance = RightScale::Sender.instance
788
- flexmock(@instance.connectivity_checker).should_receive(:check).once
789
- @broker.should_receive(:publish).and_return(@broker_ids).times(3)
790
- @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
791
- result = RightScale::OperationResult.from_results(response)
609
+ it "should reject keys other than account and shard" do
610
+ target = {"scope" => {:shard => 1, "account" => 1, :other => 2}}
611
+ lambda { @sender.send(:validate_target, target, true) }.
612
+ should raise_error(ArgumentError, /Invalid target scope/)
792
613
  end
793
- @instance.pending_requests.empty?.should be_false
794
- EM.add_timer(1) do
795
- EM.stop
796
- result.non_delivery?.should be_true
797
- result.content.should == RightScale::OperationResult::RETRY_TIMEOUT
798
- @instance.pending_requests.empty?.should be_true
614
+
615
+ it "should reject empty hash" do
616
+ lambda { @sender.send(:validate_target, {:scope => {}}, true) }.
617
+ should raise_error(ArgumentError, /Invalid target scope/)
799
618
  end
800
619
  end
801
- end
802
620
 
803
- it "should return non-delivery after timeout if any attempt failed due to non-delivery" do
804
- pending 'Too difficult to get timing right for Windows' if RightScale::Platform.windows?
805
- EM.run do
806
- result = RightScale::OperationResult.success
807
- @log.should_receive(:warning).once
808
- @agent.should_receive(:options).and_return({:retry_timeout => 0.6, :retry_interval => 0.1})
809
- RightScale::Sender.new(@agent)
810
- @instance = RightScale::Sender.instance
811
- flexmock(@instance.connectivity_checker).should_receive(:check).once
812
- @broker.should_receive(:publish).and_return(@broker_ids).times(3)
813
- @instance.send_retryable_request('/welcome/aboard', 'iZac') do |response|
814
- result = RightScale::OperationResult.from_results(response)
621
+ context "and multiple are specified" do
622
+ it "should accept scope and tags" do
623
+ @sender.send(:validate_target, {:scope => {:shard => 1}, :tags => []}, true).should be_true
815
624
  end
816
- @instance.pending_requests.empty?.should be_false
817
- token = @instance.pending_requests.keys.last
818
- non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::TARGET_NOT_CONNECTED)
819
- @instance.handle_response(RightScale::Result.new(token, 'iZac', non_delivery, "from"))
820
- EM.add_timer(1) do
821
- EM.stop
822
- result.non_delivery?.should be_true
823
- result.content.should == non_delivery.content
824
- @instance.pending_requests.empty?.should be_true
625
+
626
+ it "should accept scope, tags, and selector" do
627
+ target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
628
+ @sender.send(:validate_target, target, true).should be_true
629
+ end
630
+
631
+ it "should reject selector if not allowed" do
632
+ target = {:scope => {:shard => 1}, :tags => ["tag"], :selector => :all}
633
+ lambda { @sender.send(:validate_target, target, false) }.
634
+ should raise_error(ArgumentError, /Invalid target hash/)
825
635
  end
826
636
  end
827
- end
828
637
 
829
- it "should retry with same request expires_at value" do
830
- EM.run do
831
- token = 'abc'
832
- expires_at = nil
833
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return(token).twice
834
- @agent.should_receive(:options).and_return({:retry_timeout => 0.5, :retry_interval => 0.1})
835
- RightScale::Sender.new(@agent)
836
- @instance = RightScale::Sender.instance
837
- flexmock(@instance.connectivity_checker).should_receive(:check).once
838
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
839
- request.expires_at.should == (expires_at ||= request.expires_at)
840
- end, hsh(:persistent => false, :mandatory => true)).and_return(@broker_ids).twice
841
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
842
- EM.add_timer(0.2) { EM.stop }
638
+ it "should reject keys other than selector, scope, and tags" do
639
+ target = {:scope => {:shard => 1}, :tags => [], :selector => :all, :other => 2}
640
+ lambda { @sender.send(:validate_target, target, true) }.
641
+ should raise_error(ArgumentError, /Invalid target hash/)
642
+ end
643
+
644
+ it "should reject empty hash" do
645
+ lambda { @sender.send(:validate_target, {}, true) }.
646
+ should raise_error(ArgumentError, /Invalid target hash/)
647
+ end
648
+
649
+ it "should reject value that is not nil, string, or hash" do
650
+ lambda { @sender.send(:validate_target, [], true) }.
651
+ should raise_error(ArgumentError, /Invalid target/)
843
652
  end
844
653
  end
654
+ end
655
+
656
+ context :http do
845
657
 
846
- describe "and checking connection status" do
658
+ context :http_send do
847
659
  before(:each) do
848
- @broker_id = "rs-broker-host-123"
849
- @broker_ids = [@broker_id]
660
+ @received_at = Time.now
661
+ flexmock(Time).should_receive(:now).and_return(@received_at)
662
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@token).by_default
663
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
664
+ @response = nil
665
+ @callback = lambda { |response| @response = response }
850
666
  end
851
667
 
852
- it "should not check connection if check already in progress" do
853
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).never
854
- @instance.connectivity_checker.ping_timer = true
855
- flexmock(@instance).should_receive(:publish).never
856
- @instance.connectivity_checker.check(@broker_ids)
668
+ it "sends request using configured client" do
669
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, @callback)
670
+ @client.should_receive(:push).with(@type, @payload, @target, @token).and_return(nil).once
671
+ @sender.send(:http_send, :send_push, @target, @packet, @received_at, @callback).should be_true
857
672
  end
858
673
 
859
- it "should publish ping to mapper" do
860
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
861
- flexmock(@instance).should_receive(:publish).with(on { |request| request.type.should == "/mapper/ping" },
862
- @broker_ids).and_return(@broker_ids).once
863
- @instance.connectivity_checker.check(@broker_id)
864
- @instance.pending_requests.size.should == 1
674
+ it "responds with success result containing response" do
675
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return("result").once
676
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
677
+ @response.token.should == @token
678
+ @response.results.success?.should be_true
679
+ @response.results.content.should == "result"
680
+ @response.from.should == @target
681
+ @response.received_at.should == @received_at.to_f
865
682
  end
866
683
 
867
- it "should not make any connection changes if receive ping response" do
868
- flexmock(RightScale::AgentIdentity).should_receive(:generate).and_return('abc').once
869
- @timer.should_receive(:cancel).once
870
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).once
871
- flexmock(@instance).should_receive(:publish).and_return(@broker_ids).once
872
- @instance.connectivity_checker.check(@broker_id)
873
- @instance.connectivity_checker.ping_timer.should == @timer
874
- @instance.pending_requests.size.should == 1
875
- @instance.pending_requests['abc'].response_handler.call(nil)
876
- @instance.connectivity_checker.ping_timer.should == nil
684
+ it "responds with success result when response is nil" do
685
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return(nil).once
686
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
687
+ @response.token.should == @token
688
+ @response.results.success?.should be_true
689
+ @response.results.content.should be_nil
690
+ @response.from.should == @target
691
+ @response.received_at.should == @received_at.to_f
877
692
  end
878
693
 
879
- it "should try to reconnect if ping times out repeatedly" do
880
- @log.should_receive(:warning).with(/timed out after 30 seconds/).twice
881
- @log.should_receive(:error).with(/reached maximum of 3 timeouts/).once
882
- flexmock(EM::Timer).should_receive(:new).and_yield.times(3)
883
- flexmock(@agent).should_receive(:connect).once
884
- @instance.connectivity_checker.check(@broker_id)
885
- @instance.connectivity_checker.check(@broker_id)
886
- @instance.connectivity_checker.check(@broker_id)
887
- @instance.connectivity_checker.ping_timer.should == nil
694
+ it "responds asynchronously if requested" do
695
+ @sender = create_sender(:http, :async_response => true)
696
+ flexmock(EM).should_receive(:next_tick).and_yield.once
697
+ @client.should_receive(:request).with(@type, @payload, @target, @token).and_return(nil).once
698
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
699
+ @response.token.should == @token
700
+ @response.results.success?.should be_true
701
+ @response.results.content.should be_nil
702
+ @response.from.should == @target
703
+ @response.received_at.should == @received_at.to_f
888
704
  end
889
705
 
890
- it "should log error if attempt to reconnect fails" do
891
- @log.should_receive(:warning).with(/timed out after 30 seconds/).twice
892
- @log.should_receive(:error).with(/Failed to reconnect/, Exception, :trace).once
893
- flexmock(@agent).should_receive(:connect).and_raise(Exception)
894
- flexmock(EM::Timer).should_receive(:new).and_yield.times(3)
895
- @instance.connectivity_checker.check(@broker_id)
896
- @instance.connectivity_checker.check(@broker_id)
897
- @instance.connectivity_checker.check(@broker_id)
706
+ context "when fails" do
707
+ context "with connectivity error" do
708
+ it "queues request if queueing and does not respond" do
709
+ @sender = create_sender(:http, :offline_queueing => true)
710
+ @sender.initialize_offline_queue
711
+ @sender.enable_offline_mode
712
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::ConnectivityFailure, "disconnected").once
713
+ flexmock(@sender.offline_handler).should_receive(:queue_request).with(:send_request, @type,
714
+ @payload, @target, @callback).once
715
+ flexmock(@sender).should_receive(:handle_response).never
716
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
717
+ end
718
+
719
+ it "responds with retry result if not queueing" do
720
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::ConnectivityFailure, "Server not responding").once
721
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
722
+ @response.results.retry?.should be_true
723
+ @response.results.content.should == "Server not responding"
724
+ end
725
+ end
726
+
727
+ it "responds with retry result if retryable error" do
728
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::RetryableError, "try again").once
729
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
730
+ @response.results.retry?.should be_true
731
+ @response.results.content.should == "try again"
732
+ end
733
+
734
+ it "responds with error result if internal error" do
735
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::InternalServerError.new("unprocessable", "Router")).once
736
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
737
+ @response.results.error?.should be_true
738
+ @response.results.content.should == "Router internal error"
739
+ end
740
+
741
+ it "does not respond to request if terminating" do
742
+ @client.should_receive(:request).and_raise(RightScale::Exceptions::Terminating, "going down").once
743
+ flexmock(@sender).should_receive(:handle_response).never
744
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
745
+ end
746
+
747
+ it "responds with error result if HTTP error" do
748
+ @client.should_receive(:request).and_raise(RestExceptionMock.new(400, "bad data")).once
749
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
750
+ @response.results.error?.should be_true
751
+ @response.results.content.should == "400 Bad Request: bad data"
752
+ end
753
+
754
+ it "responds with error result if unexpected error" do
755
+ @log.should_receive(:error).with(/Failed to send/, StandardError, :trace).once
756
+ @client.should_receive(:request).and_raise(StandardError, "unexpected").once
757
+ @sender.send(:http_send, :send_request, @target, @packet, @received_at, @callback).should be_true
758
+ @response.results.error?.should be_true
759
+ @response.results.content.should == "Agent agent internal error"
760
+ end
898
761
  end
899
762
  end
900
763
  end
901
- end
902
764
 
903
- describe "when making a send_persistent_request" do
904
- before(:each) do
905
- @timer = flexmock("timer")
906
- flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
907
- @broker_id = "rs-broker-host-123"
908
- @broker_ids = [@broker_id]
909
- @broker = flexmock("Broker", :subscribe => true, :publish => @broker_ids, :connected? => true,
910
- :identity_parts => ["host", 123, 0, 0]).by_default
911
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker,
912
- :options => {:ping_interval => 0, :time_to_live => 100}).by_default
913
- RightScale::Sender.new(@agent)
914
- @instance = RightScale::Sender.instance
915
- @instance.initialize_offline_queue
916
- end
765
+ context :amqp do
766
+ before(:each) do
767
+ @sender = create_sender(:amqp)
768
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@token).by_default
769
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, nil)
770
+ @received_at = Time.now
771
+ flexmock(Time).should_receive(:now).and_return(@received_at)
772
+ @response = nil
773
+ @callback = lambda { |response| @response = response }
774
+ end
917
775
 
918
- it "should create a Request object" do
919
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
920
- request.class.should == RightScale::Request
921
- end, hsh(:persistent => true, :mandatory => true)).once
922
- @instance.send_persistent_request('/welcome/aboard', 'iZac') {|_|}
923
- end
776
+ context :amqp_send do
777
+ it "stores pending request for use in responding if callback specified" do
778
+ @packet = @sender.build_packet(:send_push, @type, @payload, @target, @token, @callback)
779
+ flexmock(@sender).should_receive(:amqp_send_once)
780
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
781
+ @sender.pending_requests[@token].should_not be_nil
782
+ end
924
783
 
925
- it "should set correct attributes on the request message" do
926
- flexmock(Time).should_receive(:now).and_return(Time.at(1000000))
927
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
928
- request.type.should == '/welcome/aboard'
929
- request.token.should_not be_nil
930
- request.persistent.should be_true
931
- request.from.should == 'agent'
932
- request.target.should be_nil
933
- request.expires_at.should == 0
934
- end, hsh(:persistent => true, :mandatory => true)).once
935
- @instance.send_persistent_request('/welcome/aboard', 'iZac') {|_|}
936
- end
784
+ it "does not store pending request if callback not specified" do
785
+ flexmock(@sender).should_receive(:amqp_send_once)
786
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
787
+ @sender.pending_requests[@token].should be_nil
788
+ end
937
789
 
938
- it "should set the correct target if specified" do
939
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
940
- request.target.should == 'my-target'
941
- end, hsh(:persistent => true, :mandatory => true)).once
942
- @instance.send_persistent_request('/welcome/aboard', 'iZac', 'my-target') {|_|}
943
- end
790
+ it "publishes without retry capability if send_push" do
791
+ flexmock(@sender).should_receive(:amqp_send_once).with(@packet).once
792
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
793
+ end
944
794
 
945
- it "should set the correct target selectors if specified" do
946
- @broker.should_receive(:publish).with(hsh(:name => "request"), on do |request|
947
- request.tags.should == ['tag']
948
- request.selector.should == :any
949
- request.scope.should == {:account => 123}
950
- end, hsh(:persistent => true, :mandatory => true)).once
951
- @instance.send_persistent_request('/welcome/aboard', 'iZac', :tags => ['tag'], :scope => {:account => 123}) {|_|}
952
- end
795
+ it "publishes with retry capability if send_request" do
796
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
797
+ flexmock(@sender).should_receive(:amqp_send_retry).with(@packet, @token).once
798
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
799
+ end
953
800
 
954
- it "should not set up for retrying the request" do
955
- flexmock(@instance).should_receive(:publish_with_timeout_retry).never
956
- @instance.send_persistent_request('/welcome/aboard', 'iZac', 'my-target') {|_|}
957
- end
801
+ context "when fails" do
802
+
803
+ context "with offline error" do
804
+ it "submits request to offline handler if queuing" do
805
+ @sender = create_sender(:amqp, :offline_queueing => true)
806
+ @sender.initialize_offline_queue
807
+ @sender.enable_offline_mode
808
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::TemporarilyOffline).once
809
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, nil).should be_true
810
+ @sender.offline_handler.queue.size.should == 1
811
+ @sender.pending_requests[@token].should be_nil
812
+ end
813
+
814
+ it "responds with retry result if not queueing" do
815
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::TemporarilyOffline).once
816
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
817
+ @sender.offline_handler.queue.size.should == 0
818
+ @sender.pending_requests[@token].should_not be_nil # because send_push does not get deleted when deliver
819
+ @response.results.retry?.should be_true
820
+ @response.results.content.should == "lost RightNet connectivity"
821
+ end
822
+ end
958
823
 
959
- it "should not allow a selector target" do
960
- lambda { @instance.send_retryable_request('/welcome/aboard', 'iZac', :selector => :all) }.should raise_error(ArgumentError)
961
- end
824
+ it "responds with non-delivery result if send failure" do
825
+ flexmock(@sender).should_receive(:amqp_send_once).and_raise(RightScale::Sender::SendFailure).once
826
+ @sender.send(:amqp_send, :send_push, @target, @packet, @received_at, @callback).should be_true
827
+ @sender.pending_requests[@token].should_not be_nil # because send_push does not get deleted when deliver
828
+ @response.results.non_delivery?.should be_true
829
+ @response.results.content.should == "send failed unexpectedly"
830
+ end
831
+ end
832
+ end
962
833
 
963
- it "should raise error if there is no callback block" do
964
- lambda { @instance.send_persistent_request('/welcome/aboard', 'iZac') }.should raise_error(ArgumentError)
965
- end
966
- end
834
+ context :amqp_send_once do
835
+ it "publishes request to request queue" do
836
+ @client.should_receive(:publish).with(hsh(:name => "request"), @packet, hsh(:persistent => true,
837
+ :mandatory => true, :broker_ids => nil)).and_return(@broker_ids).once
838
+ @sender.send(:amqp_send_once, @packet, @broker_ids).should == @broker_ids
839
+ end
967
840
 
968
- describe "when handling a response" do
969
- before(:each) do
970
- flexmock(EM).should_receive(:defer).and_yield.by_default
971
- @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
972
- :identity_parts => ["host", 123, 0, 0]).by_default
973
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
974
- RightScale::Sender.new(@agent)
975
- @instance = RightScale::Sender.instance
976
- flexmock(RightScale::AgentIdentity, :generate => 'token1')
977
- end
841
+ context "when fails" do
842
+ it "raises TemporarilyOffline if no connected brokers" do
843
+ @log.should_receive(:error).with(/Failed to publish/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
844
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once
845
+ lambda { @sender.send(:amqp_send_once, @packet) }.should raise_error(RightScale::Sender::TemporarilyOffline)
846
+ end
978
847
 
979
- it "should deliver the response for a Request" do
980
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
981
- response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
982
- flexmock(@instance).should_receive(:deliver).with(response, RightScale::Sender::PendingRequest).once
983
- @instance.handle_response(response)
984
- end
848
+ it "raises SendFailure if unexpected exception" do
849
+ @log.should_receive(:error).with(/Failed to publish/, StandardError, :trace).once
850
+ @client.should_receive(:publish).and_raise(StandardError, "unexpected").once
851
+ lambda { @sender.send(:amqp_send_once, @packet) }.should raise_error(RightScale::Sender::SendFailure)
852
+ end
853
+ end
854
+ end
985
855
 
986
- it "should deliver the response for a Push" do
987
- @instance.send_push('/welcome/aboard', 'iZac') {|_|}
988
- response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
989
- flexmock(@instance).should_receive(:deliver).with(response, RightScale::Sender::PendingRequest).once
990
- @instance.handle_response(response)
991
- end
856
+ context :amqp_send_retry do
857
+ before(:each) do
858
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return("retry token")
859
+ @packet = @sender.build_packet(:send_request, @type, @payload, @target, @token, @callback)
860
+ end
992
861
 
993
- it "should not deliver TARGET_NOT_CONNECTED response for send_retryable_request" do
994
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
995
- flexmock(@instance).should_receive(:deliver).never
996
- non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::TARGET_NOT_CONNECTED)
997
- response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
998
- @instance.handle_response(response)
999
- end
862
+ it "publishes request to request queue" do
863
+ @client.should_receive(:publish).with(hsh(:name => "request"), @packet, hsh(:persistent => nil,
864
+ :mandatory => true, :broker_ids => nil)).and_return(@broker_ids).once
865
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
866
+ end
1000
867
 
1001
- it "should record non-delivery regardless of whether there is a response handler" do
1002
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
1003
- non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
1004
- response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
1005
- @instance.handle_response(response)
1006
- @instance.instance_variable_get(:@non_delivery_stats).total.should == 1
1007
- end
868
+ it "does not rescue if publish fails" do
869
+ @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
870
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once
871
+ lambda { @sender.send(:amqp_send_retry, @packet, @token) }.should raise_error(RightScale::Sender::TemporarilyOffline)
872
+ end
1008
873
 
1009
- it "should log non-delivery if there is no response handler" do
1010
- @log.should_receive(:info).with(/Non-delivery of/).once
1011
- @instance.send_push('/welcome/aboard', 'iZac')
1012
- non_delivery = RightScale::OperationResult.non_delivery(RightScale::OperationResult::NO_ROUTE_TO_TARGET)
1013
- response = RightScale::Result.new('token1', 'to', non_delivery, 'target1')
1014
- @instance.handle_response(response)
1015
- end
874
+ it "does not retry if retry timeout not set" do
875
+ @sender = create_sender(:amqp, :retry_interval => 10)
876
+ @client.should_receive(:publish).once
877
+ flexmock(EM).should_receive(:add_timer).never
878
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
879
+ end
1016
880
 
1017
- it "should log a debug message if request no longer pending" do
1018
- @log.should_receive(:debug).with(/No pending request for response/).once
1019
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
1020
- @instance.pending_requests['token1'].should_not be_nil
1021
- @instance.pending_requests['token2'].should be_nil
1022
- response = RightScale::Result.new('token2', 'to', RightScale::OperationResult.success, 'target1')
1023
- @instance.handle_response(response)
1024
- end
1025
- end
881
+ it "does not retry if retry interval not set" do
882
+ @sender = create_sender(:amqp, :retry_timeout => 60)
883
+ @client.should_receive(:publish).once
884
+ flexmock(EM).should_receive(:add_timer).never
885
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
886
+ end
1026
887
 
1027
- describe "when delivering a response" do
1028
- before(:each) do
1029
- flexmock(EM).should_receive(:defer).and_yield.by_default
1030
- @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
1031
- :identity_parts => ["host", 123, 0, 0]).by_default
1032
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:ping_interval => 0}).by_default
1033
- RightScale::Sender.new(@agent)
1034
- @instance = RightScale::Sender.instance
1035
- flexmock(RightScale::AgentIdentity, :generate => 'token1')
1036
- end
888
+ context "when retry enabled" do
889
+ it "uses timer to wait for a response" do
890
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
891
+ @client.should_receive(:publish).once
892
+ flexmock(EM).should_receive(:add_timer).once
893
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
894
+ end
1037
895
 
1038
- it "should delete all associated pending Request requests" do
1039
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
1040
- @instance.pending_requests['token1'].should_not be_nil
1041
- response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
1042
- @instance.handle_response(response)
1043
- @instance.pending_requests['token1'].should be_nil
1044
- end
896
+ it "stops retrying if response was received" do
897
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
898
+ @client.should_receive(:publish).once
899
+ flexmock(EM).should_receive(:add_timer).and_yield.once
900
+ @sender.send(:amqp_send_retry, @packet, @token).should be_true
901
+ @sender.pending_requests[@token].should be_nil
902
+ end
1045
903
 
1046
- it "should not delete any pending Push requests" do
1047
- @instance.send_push('/welcome/aboard', 'iZac') {|_|}
1048
- @instance.pending_requests['token1'].should_not be_nil
1049
- response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
1050
- @instance.handle_response(response)
1051
- @instance.pending_requests['token1'].should_not be_nil
1052
- end
904
+ it "keeps retrying if has not exceeded retry timeout" do
905
+ EM.run do
906
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
907
+ @client.should_receive(:publish).and_return(@broker_ids).twice
908
+ flexmock(@sender.connectivity_checker).should_receive(:check).once
909
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
910
+
911
+ EM.add_timer(0.15) do
912
+ @sender.pending_requests.empty?.should be_false
913
+ result = RightScale::Result.new(@token, nil, RightScale::OperationResult.success, nil)
914
+ @sender.handle_response(result)
915
+ end
916
+
917
+ EM.add_timer(0.3) do
918
+ EM.stop
919
+ @response.results.success?.should be_true
920
+ @sender.pending_requests.empty?.should be_true
921
+ end
922
+ end
923
+ end
1053
924
 
1054
- it "should delete any associated retry requests" do
1055
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|_|}
1056
- @instance.pending_requests['token1'].should_not be_nil
1057
- @instance.pending_requests['token2'] = @instance.pending_requests['token1'].dup
1058
- @instance.pending_requests['token2'].retry_parent = 'token1'
1059
- response = RightScale::Result.new('token2', 'to', RightScale::OperationResult.success, 'target1')
1060
- @instance.handle_response(response)
1061
- @instance.pending_requests['token1'].should be_nil
1062
- @instance.pending_requests['token2'].should be_nil
1063
- end
925
+ it "stops retrying and responds with non-delivery result if exceeds retry timeout" do
926
+ EM.run do
927
+ @sender = create_sender(:amqp, :retry_timeout => 0.1, :retry_interval => 0.1)
928
+ @client.should_receive(:publish).and_return(@broker_ids).once
929
+ @log.should_receive(:warning).once
930
+ flexmock(@sender.connectivity_checker).should_receive(:check).once
931
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
932
+
933
+ EM.add_timer(0.3) do
934
+ EM.stop
935
+ @response.results.non_delivery?.should be_true
936
+ @response.results.content.should == RightScale::OperationResult::RETRY_TIMEOUT
937
+ @sender.pending_requests.empty?.should be_true
938
+ end
939
+ end
940
+ end
1064
941
 
1065
- it "should call the response handler" do
1066
- called = 0
1067
- @instance.send_retryable_request('/welcome/aboard', 'iZac') {|response| called += 1}
1068
- response = RightScale::Result.new('token1', 'to', RightScale::OperationResult.success, 'target1')
1069
- @instance.handle_response(response)
1070
- called.should == 1
1071
- end
1072
- end
942
+ it "stops retrying if temporarily offline" do
943
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
944
+ @client.should_receive(:publish).and_return(@broker_ids).once.ordered
945
+ @client.should_receive(:publish).and_raise(RightAMQP::HABrokerClient::NoConnectedBrokers).once.ordered
946
+ flexmock(EM).should_receive(:add_timer).and_yield.once
947
+ @log.should_receive(:error).with(/Failed to publish request/, RightAMQP::HABrokerClient::NoConnectedBrokers).once
948
+ @log.should_receive(:error).with(/Failed retry.*temporarily offline/).once
949
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
950
+ @sender.pending_requests[@token].should_not be_nil
951
+ @sender.pending_requests["retry token"].should_not be_nil
952
+ end
1073
953
 
1074
- describe "when use offline queueing" do
1075
- before(:each) do
1076
- @broker = flexmock("Broker", :subscribe => true, :publish => ["broker"], :connected? => true,
1077
- :identity_parts => ["host", 123, 0, 0]).by_default
1078
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :options => {:offline_queueing => true}).by_default
1079
- RightScale::Sender.new(@agent)
1080
- @instance = RightScale::Sender.instance
1081
- @instance.initialize_offline_queue
1082
- end
954
+ it "stops retrying if there is a send failure" do
955
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
956
+ @client.should_receive(:publish).and_return(@broker_ids).once.ordered
957
+ @client.should_receive(:publish).and_raise(StandardError, "failed").once.ordered
958
+ flexmock(EM).should_receive(:add_timer).and_yield.once
959
+ @log.should_receive(:error).with(/Failed to publish request/, StandardError, :trace).once
960
+ @log.should_receive(:error).with(/Failed retry.*send failure/).once
961
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
962
+ @sender.pending_requests[@token].should_not be_nil
963
+ @sender.pending_requests["retry token"].should_not be_nil
964
+ end
1083
965
 
1084
- it 'should queue requests prior to offline handler initialization and then flush once started' do
1085
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1086
- RightScale::Sender.new(@agent)
1087
- @instance = RightScale::Sender.instance
1088
- begin
1089
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1090
- EM.run do
1091
- @instance.send_push('/dummy', 'payload')
1092
- @instance.offline_handler.offline?.should be_true
1093
- @instance.offline_handler.state.should == :created
1094
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 1
1095
- @instance.initialize_offline_queue
1096
- @broker.should_receive(:publish).once.and_return { EM.stop }
1097
- @instance.start_offline_queue
1098
- EM.add_timer(1) { EM.stop }
966
+ it "stops retrying if there is an unexpected exception" do
967
+ # This will actually call amqp_send_retry 3 times recursively because add_timer always yields immediately
968
+ @sender = create_sender(:amqp, :retry_timeout => 0.3, :retry_interval => 0.1)
969
+ @client.should_receive(:publish).and_return(@broker_ids)
970
+ @log.should_receive(:error).with(/Failed retry.*without responding/, StandardError, :trace).twice
971
+ flexmock(EM).should_receive(:add_timer).and_yield
972
+ flexmock(@sender.connectivity_checker).should_receive(:check).and_raise(StandardError).once
973
+ @sender.send(:amqp_send, :send_request, @target, @packet, @received_at, @callback).should be_true
974
+ @sender.pending_requests[@token].should_not be_nil
975
+ @sender.pending_requests["retry token"].should_not be_nil
976
+ end
1099
977
  end
1100
- ensure
1101
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
1102
978
  end
1103
979
  end
1104
980
 
1105
- it 'should not queue requests prior to offline handler startup if not offline' do
1106
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1107
- RightScale::Sender.new(@agent)
1108
- @instance = RightScale::Sender.instance
1109
- begin
1110
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1111
- EM.run do
1112
- @instance.send_push('/dummy', 'payload')
1113
- @instance.offline_handler.offline?.should be_true
1114
- @instance.offline_handler.state.should == :created
1115
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 1
1116
- @instance.initialize_offline_queue
1117
- @broker.should_receive(:publish).with(Hash, on {|arg| arg.type == "/dummy2"}, Hash).once
1118
- @instance.send_push('/dummy2', 'payload')
1119
- @instance.offline_handler.offline?.should be_false
1120
- @instance.offline_handler.mode.should == :initializing
1121
- @instance.offline_handler.state.should == :initializing
1122
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 1
1123
- @instance.offline_handler.instance_variable_get(:@queue).first[:type].should == "/dummy"
1124
- @broker.should_receive(:publish).with(Hash, on {|arg| arg.type == "/dummy"}, Hash).once
1125
- @instance.start_offline_queue
1126
- EM.add_timer(1) do
1127
- @instance.offline_handler.mode.should == :online
1128
- @instance.offline_handler.state.should == :running
1129
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 0
1130
- EM.stop
1131
- end
1132
- end
1133
- ensure
1134
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
981
+ context :deliver_response do
982
+ before(:each) do
983
+ @received_at = Time.now
984
+ flexmock(Time).should_receive(:now).and_return(@received_at)
985
+ @response = nil
986
+ @callback = lambda { |response| @response = response }
1135
987
  end
1136
- end
1137
988
 
1138
- it 'should queue requests at front if received after offline handler initialization but before startup' do
1139
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1140
- RightScale::Sender.new(@agent)
1141
- @instance = RightScale::Sender.instance
1142
- begin
1143
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1144
- EM.run do
1145
- @instance.send_push('/dummy', 'payload')
1146
- @instance.offline_handler.offline?.should be_true
1147
- @instance.offline_handler.state.should == :created
1148
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 1
1149
- @instance.initialize_offline_queue
1150
- @instance.offline_handler.offline?.should be_false
1151
- @instance.offline_handler.mode.should == :initializing
1152
- @instance.offline_handler.state.should == :initializing
1153
- @instance.enable_offline_mode
1154
- @instance.send_push('/dummy2', 'payload')
1155
- @instance.offline_handler.offline?.should be_true
1156
- @instance.offline_handler.mode.should == :offline
1157
- @instance.offline_handler.state.should == :initializing
1158
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 2
1159
- @instance.offline_handler.instance_variable_get(:@queue).first[:type].should == "/dummy2"
1160
- @instance.start_offline_queue
1161
- @instance.offline_handler.mode.should == :offline
1162
- @instance.offline_handler.state.should == :running
1163
- @broker.should_receive(:publish).with(Hash, on {|arg| arg.type == "/dummy2"}, Hash).once
1164
- @broker.should_receive(:publish).with(Hash, on {|arg| arg.type == "/dummy"}, Hash).once
1165
- @instance.disable_offline_mode
1166
- @instance.offline_handler.state.should == :flushing
1167
- EM.add_timer(1) do
1168
- @instance.offline_handler.mode.should == :online
1169
- @instance.offline_handler.state.should == :running
1170
- @instance.offline_handler.instance_variable_get(:@queue).size.should == 0
1171
- EM.stop
1172
- end
1173
- end
1174
- ensure
1175
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
989
+ it "calls the response handler" do
990
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
991
+ @sender.pending_requests[@token] = pending_request
992
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
993
+ @sender.send(:deliver_response, response, pending_request).should be_true
994
+ @response.should == response
1176
995
  end
1177
- end
1178
996
 
1179
- it 'should vote for restart after the maximum number of queued requests is reached' do
1180
- @instance.offline_handler.instance_variable_get(:@restart_vote_count).should == 0
1181
- EM.run do
1182
- @instance.enable_offline_mode
1183
- @instance.offline_handler.queue = ('*' * (RightScale::Sender::OfflineHandler::MAX_QUEUED_REQUESTS - 1)).split(//)
1184
- @instance.send_push('/dummy', 'payload')
1185
- EM.next_tick { EM.stop }
997
+ it "deletes associated pending request if is it a Request" do
998
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
999
+ @sender.pending_requests[@token] = pending_request
1000
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1001
+ @sender.send(:deliver_response, response, pending_request).should be_true
1002
+ @sender.pending_requests[@token].should be_nil
1186
1003
  end
1187
- @instance.offline_handler.queue.size.should == RightScale::Sender::OfflineHandler::MAX_QUEUED_REQUESTS
1188
- @instance.offline_handler.instance_variable_get(:@restart_vote_count).should == 1
1189
- end
1190
1004
 
1191
- it 'should vote for restart after the threshold delay is reached' do
1192
- old_vote_delay = RightScale::Sender::OfflineHandler::RESTART_VOTE_DELAY
1193
- begin
1194
- RightScale::Sender::OfflineHandler.const_set(:RESTART_VOTE_DELAY, 0.1)
1195
- @instance.offline_handler.instance_variable_get(:@restart_vote_count).should == 0
1196
- EM.run do
1197
- @instance.enable_offline_mode
1198
- @instance.send_push('/dummy', 'payload')
1199
- EM.add_timer(0.5) { EM.stop }
1200
- end
1201
- @instance.offline_handler.instance_variable_get(:@restart_vote_count).should == 1
1202
- ensure
1203
- RightScale::Sender::OfflineHandler.const_set(:RESTART_VOTE_DELAY, old_vote_delay)
1005
+ it "does not delete pending request if it is a Push" do
1006
+ pending_request = RightScale::PendingRequest.new(:send_push, @received_at, @callback)
1007
+ @sender.pending_requests[@token] = pending_request
1008
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1009
+ @sender.send(:deliver_response, response, pending_request).should be_true
1010
+ @sender.pending_requests[@token].should_not be_nil
1204
1011
  end
1205
- end
1206
1012
 
1207
- it 'should not flush queued requests until back online' do
1208
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1209
- begin
1210
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1211
- EM.run do
1212
- @instance.enable_offline_mode
1213
- @instance.send_push('/dummy', 'payload')
1214
- EM.add_timer(0.5) { EM.stop }
1215
- end
1216
- ensure
1217
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
1013
+ it "deletes any associated retry requests" do
1014
+ @parent_token = RightSupport::Data::UUID.generate
1015
+ pending_request = RightScale::PendingRequest.new(:send_request, @received_at, @callback)
1016
+ @sender.pending_requests[@token] = pending_request
1017
+ @sender.pending_requests[@token].retry_parent_token = @parent_token
1018
+ @sender.pending_requests[@parent_token] = @sender.pending_requests[@token].dup
1019
+ response = RightScale::Result.new(@token, "to", RightScale::OperationResult.success, @target)
1020
+ @sender.send(:deliver_response, response, pending_request).should be_true
1021
+ @sender.pending_requests[@token].should be_nil
1022
+ @sender.pending_requests[@parent_token].should be_nil
1218
1023
  end
1219
1024
  end
1220
1025
 
1221
- it 'should flush queued requests once back online' do
1222
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1223
- @broker.should_receive(:publish).once.and_return { EM.stop }
1224
- begin
1225
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1226
- EM.run do
1227
- @instance.enable_offline_mode
1228
- @instance.send_push('/dummy', 'payload')
1229
- @instance.disable_offline_mode
1230
- EM.add_timer(1) { EM.stop }
1231
- end
1232
- ensure
1233
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
1026
+ context :queueing? do
1027
+ it "returns true if offline handling enabled and currently in queueing mode" do
1028
+ @sender = create_sender(:http, :offline_queueing => true)
1029
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(true)
1030
+ @sender.send(:queueing?).should be_true
1234
1031
  end
1235
- end
1236
1032
 
1237
- it 'should stop flushing when going back to offline mode' do
1238
- old_flush_delay = RightScale::Sender::OfflineHandler::MAX_QUEUE_FLUSH_DELAY
1239
- begin
1240
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, 0.1)
1241
- EM.run do
1242
- @instance.enable_offline_mode
1243
- @instance.send_push('/dummy', 'payload')
1244
- @instance.disable_offline_mode
1245
- @instance.offline_handler.state.should == :flushing
1246
- @instance.offline_handler.mode.should == :offline
1247
- @instance.enable_offline_mode
1248
- @instance.offline_handler.state.should == :running
1249
- @instance.offline_handler.mode.should == :offline
1250
- EM.add_timer(1) do
1251
- @instance.offline_handler.state.should == :running
1252
- @instance.offline_handler.mode.should == :offline
1253
- EM.stop
1254
- end
1255
- end
1256
- ensure
1257
- RightScale::Sender::OfflineHandler.const_set(:MAX_QUEUE_FLUSH_DELAY, old_flush_delay)
1033
+ it "returns false if offline handling disabled" do
1034
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(true)
1035
+ @sender.send(:queueing?).should be_false
1036
+ end
1037
+
1038
+ it "returns false if offline handling enabled but not in queueing mode" do
1039
+ @sender = create_sender(:http, :offline_queueing => true)
1040
+ flexmock(@sender.offline_handler).should_receive(:queueing?).and_return(false)
1041
+ @sender.send(:queueing?).should be_false
1258
1042
  end
1259
1043
  end
1260
1044
  end
1261
-
1262
1045
  end