right_agent 2.0.7-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +82 -0
  3. data/Rakefile +113 -0
  4. data/lib/right_agent.rb +59 -0
  5. data/lib/right_agent/actor.rb +182 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +232 -0
  8. data/lib/right_agent/agent.rb +1149 -0
  9. data/lib/right_agent/agent_config.rb +480 -0
  10. data/lib/right_agent/agent_identity.rb +210 -0
  11. data/lib/right_agent/agent_tag_manager.rb +237 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/clients.rb +31 -0
  14. data/lib/right_agent/clients/api_client.rb +383 -0
  15. data/lib/right_agent/clients/auth_client.rb +247 -0
  16. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  17. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  18. data/lib/right_agent/clients/right_http_client.rb +279 -0
  19. data/lib/right_agent/clients/router_client.rb +493 -0
  20. data/lib/right_agent/command.rb +30 -0
  21. data/lib/right_agent/command/agent_manager_commands.rb +150 -0
  22. data/lib/right_agent/command/command_client.rb +136 -0
  23. data/lib/right_agent/command/command_constants.rb +33 -0
  24. data/lib/right_agent/command/command_io.rb +126 -0
  25. data/lib/right_agent/command/command_parser.rb +87 -0
  26. data/lib/right_agent/command/command_runner.rb +118 -0
  27. data/lib/right_agent/command/command_serializer.rb +63 -0
  28. data/lib/right_agent/connectivity_checker.rb +179 -0
  29. data/lib/right_agent/console.rb +65 -0
  30. data/lib/right_agent/core_payload_types.rb +44 -0
  31. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  32. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  33. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  34. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  35. data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
  36. data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
  37. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  38. data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
  39. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  40. data/lib/right_agent/core_payload_types/login_user.rb +79 -0
  41. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  42. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
  43. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  44. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  45. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
  46. data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
  47. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  48. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  49. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  50. data/lib/right_agent/daemonize.rb +35 -0
  51. data/lib/right_agent/dispatched_cache.rb +109 -0
  52. data/lib/right_agent/dispatcher.rb +272 -0
  53. data/lib/right_agent/enrollment_result.rb +221 -0
  54. data/lib/right_agent/exceptions.rb +87 -0
  55. data/lib/right_agent/history.rb +145 -0
  56. data/lib/right_agent/log.rb +460 -0
  57. data/lib/right_agent/minimal.rb +46 -0
  58. data/lib/right_agent/monkey_patches.rb +30 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  64. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  65. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  66. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
  67. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  68. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  69. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  70. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  71. data/lib/right_agent/multiplexer.rb +102 -0
  72. data/lib/right_agent/offline_handler.rb +270 -0
  73. data/lib/right_agent/operation_result.rb +300 -0
  74. data/lib/right_agent/packets.rb +673 -0
  75. data/lib/right_agent/payload_formatter.rb +104 -0
  76. data/lib/right_agent/pending_requests.rb +128 -0
  77. data/lib/right_agent/pid_file.rb +159 -0
  78. data/lib/right_agent/platform.rb +770 -0
  79. data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
  80. data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
  81. data/lib/right_agent/platform/unix/platform.rb +226 -0
  82. data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
  83. data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
  84. data/lib/right_agent/platform/windows/platform.rb +1808 -0
  85. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  86. data/lib/right_agent/retryable_request.rb +195 -0
  87. data/lib/right_agent/scripts/agent_controller.rb +543 -0
  88. data/lib/right_agent/scripts/agent_deployer.rb +400 -0
  89. data/lib/right_agent/scripts/common_parser.rb +160 -0
  90. data/lib/right_agent/scripts/log_level_manager.rb +192 -0
  91. data/lib/right_agent/scripts/stats_manager.rb +268 -0
  92. data/lib/right_agent/scripts/usage.rb +58 -0
  93. data/lib/right_agent/secure_identity.rb +92 -0
  94. data/lib/right_agent/security.rb +32 -0
  95. data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
  96. data/lib/right_agent/security/certificate.rb +102 -0
  97. data/lib/right_agent/security/certificate_cache.rb +89 -0
  98. data/lib/right_agent/security/distinguished_name.rb +56 -0
  99. data/lib/right_agent/security/encrypted_document.rb +83 -0
  100. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  101. data/lib/right_agent/security/signature.rb +86 -0
  102. data/lib/right_agent/security/static_certificate_store.rb +85 -0
  103. data/lib/right_agent/sender.rb +792 -0
  104. data/lib/right_agent/serialize.rb +29 -0
  105. data/lib/right_agent/serialize/message_pack.rb +107 -0
  106. data/lib/right_agent/serialize/secure_serializer.rb +151 -0
  107. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  108. data/lib/right_agent/serialize/serializable.rb +151 -0
  109. data/lib/right_agent/serialize/serializer.rb +159 -0
  110. data/lib/right_agent/subprocess.rb +38 -0
  111. data/lib/right_agent/tracer.rb +124 -0
  112. data/right_agent.gemspec +101 -0
  113. data/spec/actor_registry_spec.rb +80 -0
  114. data/spec/actor_spec.rb +162 -0
  115. data/spec/agent_config_spec.rb +235 -0
  116. data/spec/agent_identity_spec.rb +78 -0
  117. data/spec/agent_spec.rb +734 -0
  118. data/spec/agent_tag_manager_spec.rb +319 -0
  119. data/spec/clients/api_client_spec.rb +423 -0
  120. data/spec/clients/auth_client_spec.rb +272 -0
  121. data/spec/clients/balanced_http_client_spec.rb +576 -0
  122. data/spec/clients/base_retry_client_spec.rb +635 -0
  123. data/spec/clients/router_client_spec.rb +594 -0
  124. data/spec/clients/spec_helper.rb +111 -0
  125. data/spec/command/agent_manager_commands_spec.rb +51 -0
  126. data/spec/command/command_io_spec.rb +93 -0
  127. data/spec/command/command_parser_spec.rb +79 -0
  128. data/spec/command/command_runner_spec.rb +107 -0
  129. data/spec/command/command_serializer_spec.rb +51 -0
  130. data/spec/connectivity_checker_spec.rb +83 -0
  131. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  132. data/spec/core_payload_types/dev_repository_spec.rb +33 -0
  133. data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
  134. data/spec/core_payload_types/login_user_spec.rb +102 -0
  135. data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
  136. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  137. data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
  138. data/spec/core_payload_types/spec_helper.rb +23 -0
  139. data/spec/dispatched_cache_spec.rb +136 -0
  140. data/spec/dispatcher_spec.rb +324 -0
  141. data/spec/enrollment_result_spec.rb +53 -0
  142. data/spec/history_spec.rb +246 -0
  143. data/spec/log_spec.rb +192 -0
  144. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  145. data/spec/multiplexer_spec.rb +48 -0
  146. data/spec/offline_handler_spec.rb +340 -0
  147. data/spec/operation_result_spec.rb +208 -0
  148. data/spec/packets_spec.rb +461 -0
  149. data/spec/pending_requests_spec.rb +136 -0
  150. data/spec/platform/spec_helper.rb +216 -0
  151. data/spec/platform/unix/darwin/platform_spec.rb +181 -0
  152. data/spec/platform/unix/linux/platform_spec.rb +540 -0
  153. data/spec/platform/unix/spec_helper.rb +149 -0
  154. data/spec/platform/windows/mingw/platform_spec.rb +222 -0
  155. data/spec/platform/windows/mswin/platform_spec.rb +259 -0
  156. data/spec/platform/windows/spec_helper.rb +720 -0
  157. data/spec/retryable_request_spec.rb +306 -0
  158. data/spec/secure_identity_spec.rb +50 -0
  159. data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
  160. data/spec/security/certificate_cache_spec.rb +71 -0
  161. data/spec/security/certificate_spec.rb +49 -0
  162. data/spec/security/distinguished_name_spec.rb +46 -0
  163. data/spec/security/encrypted_document_spec.rb +55 -0
  164. data/spec/security/rsa_key_pair_spec.rb +55 -0
  165. data/spec/security/signature_spec.rb +66 -0
  166. data/spec/security/static_certificate_store_spec.rb +58 -0
  167. data/spec/sender_spec.rb +1045 -0
  168. data/spec/serialize/message_pack_spec.rb +131 -0
  169. data/spec/serialize/secure_serializer_spec.rb +132 -0
  170. data/spec/serialize/serializable_spec.rb +90 -0
  171. data/spec/serialize/serializer_spec.rb +197 -0
  172. data/spec/spec.opts +2 -0
  173. data/spec/spec.win32.opts +1 -0
  174. data/spec/spec_helper.rb +130 -0
  175. data/spec/tracer_spec.rb +114 -0
  176. metadata +447 -0
@@ -0,0 +1,635 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'base_retry_client'))
26
+
27
+ describe RightScale::BaseRetryClient do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ before(:each) do
32
+ @log = flexmock(RightScale::Log)
33
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
34
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
35
+ @url = "http://test.com"
36
+ @timer = flexmock("timer", :cancel => true, :interval= => 0).by_default
37
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).by_default
38
+ @http_client = flexmock("http client", :get => true, :check_health => true).by_default
39
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).by_default
40
+ @auth_header = {"Authorization" => "Bearer <session>"}
41
+ @auth_client = AuthClientMock.new(@url, @auth_header)
42
+ @client = RightScale::BaseRetryClient.new
43
+ @options = {:api_version => "2.0"}
44
+ @client.init(:test, @auth_client, @options)
45
+ end
46
+
47
+ context :init do
48
+ it "requires auth client to produce URL for specified type of client" do
49
+ lambda { @client.init(:bogus, @auth_client, @options) }.should \
50
+ raise_error(ArgumentError, "Auth client does not support server type :bogus")
51
+ end
52
+
53
+ it "requires :api_version option" do
54
+ lambda { @client.init(:test, @auth_client, {}) }.should \
55
+ raise_error(ArgumentError, ":api_version option missing")
56
+ end
57
+
58
+ it "initializes options" do
59
+ @options = {
60
+ :server_name => "Test",
61
+ :api_version => "2.0",
62
+ :open_timeout => 1,
63
+ :request_timeout => 2,
64
+ :retry_timeout => 3,
65
+ :retry_intervals => [1, 2, 3],
66
+ :reconnect_interval => 4 }
67
+ @client.init(:test, @auth_client, @options).should be_true
68
+ options = @client.instance_variable_get(:@options)
69
+ options[:server_name].should == "Test"
70
+ options[:api_version].should == "2.0"
71
+ options[:open_timeout].should == 1
72
+ options[:request_timeout].should == 2
73
+ options[:retry_timeout].should == 3
74
+ options[:retry_intervals].should == [1, 2, 3]
75
+ options[:reconnect_interval].should == 4
76
+ end
77
+
78
+ it "initializes options to defaults if no value specified" do
79
+ options = @client.instance_variable_get(:@options)
80
+ options[:server_name].should == "test"
81
+ options[:open_timeout].should == 2
82
+ options[:request_timeout].should == 35
83
+ options[:retry_timeout].should == 25
84
+ options[:retry_intervals].should == [4, 12, 36]
85
+ options[:reconnect_interval].should == 15
86
+ end
87
+
88
+ it "does not default some options" do
89
+ options = @client.instance_variable_get(:@options)
90
+ options[:retry_enabled].should be_nil
91
+ options[:filter_params].should be_nil
92
+ options[:exception_callback].should be_nil
93
+ end
94
+
95
+ it "initiates connection and enables use if connected" do
96
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).once
97
+ @http_client.should_receive(:check_health).once
98
+ flexmock(@client).should_receive(:enable_use).once
99
+ flexmock(@client).should_receive(:reconnect).never
100
+ @client.init(:test, @auth_client, @options).should be_true
101
+ @client.state.should == :connected
102
+ end
103
+
104
+ it "sets up for reconnect if not connected" do
105
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).once
106
+ e = RightScale::BalancedHttpClient::NotResponding.new(nil, RestExceptionMock.new(503))
107
+ @http_client.should_receive(:check_health).and_raise(e)
108
+ @log.should_receive(:error)
109
+ flexmock(@client).should_receive(:enable_use).never
110
+ flexmock(@client).should_receive(:reconnect).once
111
+ @client.init(:test, @auth_client, @options).should be_false
112
+ @client.state.should == :disconnected
113
+ end
114
+ end
115
+
116
+ context :status do
117
+ it "stores callback" do
118
+ callback = lambda { |_, _| }
119
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
120
+ @client.status(&callback)
121
+ @client.instance_variable_get(:@status_callbacks).size.should == 1
122
+ @client.instance_variable_get(:@status_callbacks)[0].should == callback
123
+ end
124
+
125
+ it "treats callback as optional" do
126
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
127
+ @client.status
128
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
129
+ end
130
+
131
+ it "returns current state" do
132
+ @client.status.should == :connected
133
+ end
134
+ end
135
+
136
+ context :communicated do
137
+ it "stores callback" do
138
+ callback = lambda { |_, _| }
139
+ @client.instance_variable_get(:@communicated_callbacks).size.should == 0
140
+ @client.communicated(&callback)
141
+ @client.instance_variable_get(:@communicated_callbacks).size.should == 1
142
+ @client.instance_variable_get(:@communicated_callbacks)[0].should == callback
143
+ end
144
+ end
145
+
146
+ context :close do
147
+ it "sets state to :closed by default" do
148
+ @client.close
149
+ @client.state.should == :closed
150
+ end
151
+
152
+ it "cancels reconnect timer" do
153
+ @client.send(:reconnect)
154
+ @timer.should_receive(:cancel).once
155
+ @client.close
156
+ end
157
+
158
+ it "sets state to :closing if only closing for receiving" do
159
+ @client.close(:receive)
160
+ @client.state.should == :closing
161
+ end
162
+ end
163
+
164
+ context :state= do
165
+ before(:each) do
166
+ @client.instance_variable_set(:@state, :pending)
167
+ end
168
+
169
+ it "raises exception if state transition is invalid" do
170
+ @client.send(:state=, :connected)
171
+ lambda { @client.send(:state=, :pending) }.should raise_error(ArgumentError, "Invalid state transition: :connected -> :pending")
172
+ end
173
+
174
+ [:pending, :closed].each do |state|
175
+ context state do
176
+ it "stores new state" do
177
+ @client.send(:state=, state)
178
+ @client.state.should == state
179
+ end
180
+ end
181
+ end
182
+
183
+ [:connected, :disconnected, :failed].each do |state|
184
+ context state do
185
+ before(:each) do
186
+ flexmock(@client).should_receive(:reconnect).by_default
187
+ end
188
+
189
+ it "stores new state" do
190
+ @client.send(:state=, state)
191
+ @client.state.should == state
192
+ end
193
+
194
+ it "stores new state" do
195
+ @client.send(:state=, state).should == state
196
+ end
197
+
198
+ context "when callbacks" do
199
+ it "makes callbacks with new state" do
200
+ callback_type = callback_state = nil
201
+ @client.status { |t, s| callback_type = t; callback_state = s }
202
+ @client.send(:state=, state)
203
+ callback_type.should == :test
204
+ callback_state.should == state
205
+ end
206
+
207
+ it "log error if callback fails" do
208
+ @log.should_receive(:error).with("Failed status callback", StandardError).once
209
+ @client.status { |t, s| raise StandardError, "test" }
210
+ @client.send(:state=, state).should == state
211
+ end
212
+ end
213
+
214
+ it "reconnects only if transitioning to :disconnected" do
215
+ flexmock(@client).should_receive(:reconnect).times(state == :disconnected ? 1 : 0)
216
+ @client.send(:state=, state)
217
+ end
218
+
219
+ it "does nothing if current state is :closed" do
220
+ flexmock(@client.instance_variable_get(:@stats)["state"]).should_receive(:update).once
221
+ @client.send(:state=, :closed)
222
+ @client.send(:state=, state).should == :closed
223
+ end
224
+
225
+ it "does nothing if current state is the same" do
226
+ flexmock(@client.instance_variable_get(:@stats)["state"]).should_receive(:update).once
227
+ @client.send(:state=, state)
228
+ @client.send(:state=, state).should == state
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ context :create_http_client do
235
+ it "obtains URL from auth client" do
236
+ @log.should_receive(:info).with("Connecting to test via \"http://test.com\"")
237
+ @client.send(:create_http_client)
238
+ end
239
+
240
+ it "creates HTTP client" do
241
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).once
242
+ @client.send(:create_http_client).should == @http_client
243
+ end
244
+
245
+ it "uses specified options" do
246
+ @options = {
247
+ :server_name => "Test",
248
+ :api_version => "2.0",
249
+ :open_timeout => 1,
250
+ :request_timeout => 2,
251
+ :filter_params => ["secret"] }
252
+ @client.init(:test, @auth_client, @options)
253
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).with(@url,
254
+ on { |a| a[:server_name] == "Test" &&
255
+ a[:api_version] == "2.0" &&
256
+ a[:open_timeout] == 1 &&
257
+ a[:request_timeout] == 2 &&
258
+ a[:filter_params] == ["secret"] }).and_return(@http_client).once
259
+ @client.send(:create_http_client).should == @http_client
260
+ end
261
+ end
262
+
263
+ context :enable_use do
264
+ it "should return true" do
265
+ @client.send(:enable_use).should be_true
266
+ end
267
+ end
268
+
269
+ context :check_health do
270
+ before(:each) do
271
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).by_default
272
+ @client.send(:create_http_client)
273
+ end
274
+
275
+ it "sends health check request using existing HTTP client" do
276
+ @http_client.should_receive(:check_health).once
277
+ @client.send(:check_health)
278
+ end
279
+
280
+ it "sets state to :connected" do
281
+ @client.send(:check_health)
282
+ @client.state.should == :connected
283
+ end
284
+
285
+ it "returns current state" do
286
+ @client.send(:check_health).should == :connected
287
+ end
288
+
289
+ it "sets state to :disconnected and logs if server not responding" do
290
+ e = RightScale::BalancedHttpClient::NotResponding.new("not responding", RestExceptionMock.new(503))
291
+ @http_client.should_receive(:check_health).and_raise(e).once
292
+ @log.should_receive(:error).with("Failed test health check", RestExceptionMock).once
293
+ @client.send(:check_health).should == :disconnected
294
+ @client.state.should == :disconnected
295
+ end
296
+
297
+ it "sets state to :disconnected and logs if exception unexpected" do
298
+ @log.should_receive(:error).with("Failed test health check", StandardError).once
299
+ @http_client.should_receive(:check_health).and_raise(StandardError).once
300
+ @client.send(:check_health).should == :disconnected
301
+ @client.state.should == :disconnected
302
+ end
303
+ end
304
+
305
+ context :reconnect do
306
+ before(:each) do
307
+ @client.instance_variable_set(:@reconnecting, nil)
308
+ end
309
+
310
+ it "waits random interval for initial reconnect attempt" do
311
+ flexmock(@client).should_receive(:rand).with(15).and_return(10).once
312
+ flexmock(EM::PeriodicTimer).should_receive(:new).with(10, Proc).and_return(@timer).once
313
+ @client.send(:reconnect).should be_true
314
+ end
315
+
316
+ it "attempts to connect even if currently connected" do
317
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
318
+ @client.send(:create_http_client)
319
+ @client.send(:check_health).should == :connected
320
+ flexmock(@client).should_receive(:check_health).once
321
+ @client.send(:reconnect).should be_true
322
+ end
323
+
324
+ it "recreates HTTP client and checks health" do
325
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
326
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).once
327
+ @http_client.should_receive(:check_health).once
328
+ @client.send(:reconnect).should be_true
329
+ end
330
+
331
+ context "when health check successful" do
332
+ it "enables use of client" do
333
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
334
+ flexmock(@client).should_receive(:enable_use).once
335
+ @client.send(:reconnect).should be_true
336
+ end
337
+
338
+ it "disables timer" do
339
+ @client.send(:reconnect); @client.instance_variable_set(:@reconnecting, nil) # to get @reconnect_timer initialized
340
+ @client.instance_variable_set(:@reconnecting, nil)
341
+ @timer.should_receive(:cancel).once
342
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
343
+ @client.send(:reconnect).should be_true
344
+ @client.instance_variable_get(:@reconnecting).should be_nil
345
+ end
346
+
347
+ it "does not reset timer interval" do
348
+ @timer.should_receive(:interval=).never
349
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
350
+ @client.send(:reconnect).should be_true
351
+ end
352
+ end
353
+
354
+ context "when reconnect fails" do
355
+ it "logs error if exception is raised" do
356
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
357
+ flexmock(@client).should_receive(:enable_use).and_raise(StandardError).once
358
+ @log.should_receive(:error).with("Failed test reconnect", StandardError).once
359
+ @client.send(:reconnect).should be_true
360
+ @client.state.should == :disconnected
361
+ end
362
+
363
+ it "resets the timer interval to the configured value" do
364
+ @client.send(:reconnect); @client.instance_variable_set(:@reconnecting, nil) # to get @reconnect_timer initialized
365
+ @log.should_receive(:error).with("Failed test health check", StandardError).once
366
+ @http_client.should_receive(:check_health).and_raise(StandardError).once
367
+ @timer.should_receive(:interval=).with(15).once
368
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
369
+ @client.send(:reconnect).should be_true
370
+ end
371
+ end
372
+
373
+ it "does nothing if already reconnecting" do
374
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).once
375
+ @client.send(:reconnect).should be_true
376
+ @client.instance_variable_get(:@reconnecting).should be_true
377
+ @client.send(:reconnect).should be_true
378
+ @client.instance_variable_get(:@reconnecting).should be_true
379
+ end
380
+ end
381
+
382
+ context :make_request do
383
+ before(:each) do
384
+ @path = "/foo/bar"
385
+ @params = {:some => "data"}
386
+ @request_uuid = "random uuid"
387
+ @now = Time.now
388
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
389
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@request_uuid)
390
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
391
+ @client.instance_variable_set(:@reconnecting, nil)
392
+ @client.init(:test, @auth_client, @options)
393
+ end
394
+
395
+ it "raises exception if terminating" do
396
+ @client.close
397
+ lambda { @client.send(:make_request, :get, @path) }.should raise_error(RightScale::Exceptions::Terminating)
398
+ end
399
+
400
+ it "generates a request UUID if none specified" do
401
+ @http_client.should_receive(:get).with(@path, @params, hsh(:request_uuid => @request_uuid)).once
402
+ @client.send(:make_request, :get, @path, @params)
403
+ end
404
+
405
+ it "raises exception if not connected" do
406
+ @client.send(:state=, :failed)
407
+ lambda { @client.send(:make_request, :get, @path) }.
408
+ should raise_error(RightScale::Exceptions::ConnectivityFailure, "test client not connected")
409
+ end
410
+
411
+ it "sets HTTP options for request" do
412
+ @http_client.should_receive(:get).with(@path, @params,
413
+ on { |a| a[:open_timeout] == 2 &&
414
+ a[:request_timeout] == 35 &&
415
+ a[:request_uuid] == "uuid" &&
416
+ a[:headers] == @auth_header }).once
417
+ @client.send(:make_request, :get, @path, @params, nil, "uuid")
418
+ end
419
+
420
+ it "overrides HTTP options with those supplied on request" do
421
+ @http_client.should_receive(:get).with(@path, @params,
422
+ on { |a| a[:open_timeout] == 2 &&
423
+ a[:request_timeout] == 20 &&
424
+ a[:request_uuid] == "uuid" &&
425
+ a[:headers] == @auth_header }).once
426
+ @client.send(:make_request, :get, @path, @params, nil, "uuid", {:request_timeout => 20})
427
+ end
428
+
429
+ it "makes request using HTTP client" do
430
+ @http_client.should_receive(:get).with(@path, @params, Hash).once
431
+ @client.send(:make_request, :get, @path, @params)
432
+ end
433
+
434
+ it "makes communicated callbacks" do
435
+ @http_client.should_receive(:get).once
436
+ called = 0
437
+ @client.communicated { called += 1 }
438
+ @client.send(:make_request, :get, @path, @params)
439
+ called.should == 1
440
+ end
441
+
442
+ context "when exception" do
443
+ it "handles any exceptions" do
444
+ @http_client.should_receive(:get).and_raise(StandardError, "test").once
445
+ flexmock(@client).should_receive(:handle_exception).with(StandardError, "type", @request_uuid, @now, 1).
446
+ and_raise(StandardError, "failed").once
447
+ lambda { @client.send(:make_request, :get, @path, @params, "type") }.should raise_error(StandardError, "failed")
448
+ end
449
+
450
+ it "uses path for request type if no request type specified" do
451
+ @http_client.should_receive(:get).and_raise(StandardError, "test").once
452
+ flexmock(@client).should_receive(:handle_exception).with(StandardError, @path, @request_uuid, @now, 1).
453
+ and_raise(StandardError, "failed").once
454
+ lambda { @client.send(:make_request, :get, @path, @params) }.should raise_error(StandardError, "failed")
455
+ end
456
+
457
+ it "retries if exception handling does not result in raise" do
458
+ @http_client.should_receive(:get).and_raise(StandardError, "test").twice
459
+ flexmock(@client).should_receive(:handle_exception).with(StandardError, @path, @request_uuid, @now, 1).
460
+ and_return("updated uuid").once.ordered
461
+ flexmock(@client).should_receive(:handle_exception).with(StandardError, @path, "updated uuid", @now, 2).
462
+ and_raise(StandardError, "failed").once.ordered
463
+ lambda { @client.send(:make_request, :get, @path, @params) }.should raise_error(StandardError, "failed")
464
+ end
465
+ end
466
+
467
+ it "returns result of request" do
468
+ @http_client.should_receive(:get).with(@path, @params, Hash).and_return("result").once
469
+ @client.send(:make_request, :get, @path, @params).should == "result"
470
+ end
471
+ end
472
+
473
+ context "make_request failures" do
474
+ before(:each) do
475
+ @type = "type"
476
+ @request_uuid = "random uuid"
477
+ @now = Time.now
478
+ flexmock(Time).should_receive(:now).and_return(@now, @now + 10, @now + 20)
479
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return(@request_uuid)
480
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
481
+ @client.instance_variable_set(:@reconnecting, nil)
482
+ @client.init(:test, @auth_client, @options.merge(:retry_enabled => true))
483
+ end
484
+
485
+ context :handle_exception do
486
+ context "when redirect" do
487
+ [301, 302].each do |http_code|
488
+ it "handles #{http_code} redirect" do
489
+ e = RestExceptionMock.new(http_code, "redirect")
490
+ flexmock(@client).should_receive(:handle_redirect).with(e, @type, @request_uuid).once
491
+ @client.send(:handle_exception, e, @type, @request_uuid, @now, 1)
492
+ end
493
+ end
494
+ end
495
+
496
+ it "raises if unauthorized" do
497
+ e = RestExceptionMock.new(401, "unauthorized")
498
+ lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
499
+ raise_error(RightScale::Exceptions::Unauthorized, "unauthorized")
500
+ end
501
+
502
+ it "notifies auth client and raises retryable if session expired" do
503
+ e = RestExceptionMock.new(403, "forbidden")
504
+ lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
505
+ raise_error(RightScale::Exceptions::RetryableError, "Authorization expired")
506
+ @auth_client.expired_called.should be_true
507
+ end
508
+
509
+ it "handles retry with and updates request_uuid to distinguish for retry" do
510
+ e = RestExceptionMock.new(449, "retry with")
511
+ flexmock(@client).should_receive(:handle_retry_with).with(e, @type, @request_uuid, @now, 1).
512
+ and_return("modified uuid").once
513
+ @client.send(:handle_exception, e, @type, @request_uuid, @now, 1).should == "modified uuid"
514
+ end
515
+
516
+ it "handles internal server error" do
517
+ e = RestExceptionMock.new(500, "test internal error")
518
+ lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
519
+ raise_error(RightScale::Exceptions::InternalServerError, "test internal error")
520
+ end
521
+
522
+ it "handles not responding" do
523
+ e = RightScale::BalancedHttpClient::NotResponding.new("not responding")
524
+ flexmock(@client).should_receive(:handle_not_responding).with(e, @type, @request_uuid, @now, 1).once
525
+ @client.send(:handle_exception, e, @type, @request_uuid, @now, 1)
526
+ end
527
+
528
+ it "causes other HTTP exceptions to be re-raised by returning nil" do
529
+ e = RestExceptionMock.new(400, "bad request")
530
+ @client.send(:handle_exception, e, @type, @request_uuid, @now, 1).should be_nil
531
+ end
532
+
533
+ it "causes other non-HTTP exceptions to be re-raised by returning nil" do
534
+ @client.send(:handle_exception, StandardError, @type, @request_uuid, @now, 1).should be_nil
535
+ end
536
+ end
537
+
538
+ context :handle_redirect do
539
+ it "initiates redirect by notifying auth client and raising retryable error" do
540
+ location = "http://somewhere.com"
541
+ e = RestExceptionMock.new(301, "moved permanently", {:location => location})
542
+ @log.should_receive(:info).with(/Received REDIRECT/).once.ordered
543
+ @log.should_receive(:info).with("Requesting auth client to handle redirect to #{location.inspect}").once.ordered
544
+ lambda { @client.send(:handle_redirect, e, @type, @request_uuid) }.should \
545
+ raise_error(RightScale::Exceptions::RetryableError, "moved permanently")
546
+ @auth_client.redirect_location.should == location
547
+ end
548
+
549
+ it "raises internal error if no redirect location is provided" do
550
+ e = RestExceptionMock.new(301, "moved permanently")
551
+ @log.should_receive(:info).with(/Received REDIRECT/).once
552
+ lambda { @client.send(:handle_redirect, e, @type, @request_uuid) }.should \
553
+ raise_error(RightScale::Exceptions::InternalServerError, "No redirect location provided")
554
+ end
555
+ end
556
+
557
+ context :handle_retry_with do
558
+ before(:each) do
559
+ @exception = RestExceptionMock.new(449, "retry with")
560
+ end
561
+
562
+ it "sleeps for configured interval and does not raise if retry still viable" do
563
+ @log.should_receive(:error).with(/Retrying type request/).once
564
+ flexmock(@client).should_receive(:sleep).with(4).once
565
+ @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 1)
566
+ end
567
+
568
+ it "returns modified request_uuid" do
569
+ @log.should_receive(:error)
570
+ flexmock(@client).should_receive(:sleep)
571
+ @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 1).should == "#{@request_uuid}:retry"
572
+ end
573
+
574
+ it "does not retry more than once" do
575
+ lambda { @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 2) }.should \
576
+ raise_error(RightScale::Exceptions::RetryableError)
577
+ end
578
+
579
+ it "raises retryable error if retry timed out" do
580
+ @client.init(:test, @auth_client, @options.merge(:retry_enabled => true, :retry_timeout => 10))
581
+ lambda { @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 1) }.should \
582
+ raise_error(RightScale::Exceptions::RetryableError)
583
+ end
584
+
585
+ it "raises retryable error if retry disabled" do
586
+ @client.init(:test, @auth_client, @options.merge(:retry_enabled => false))
587
+ lambda { @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 1) }.should \
588
+ raise_error(RightScale::Exceptions::RetryableError)
589
+ end
590
+ end
591
+
592
+ context :handle_not_responding do
593
+ before(:each) do
594
+ @exception = RightScale::BalancedHttpClient::NotResponding.new("Server not responding")
595
+ end
596
+
597
+ it "sleeps for configured interval and does not raise if retry still viable" do
598
+ @log.should_receive(:error).with(/Retrying type request/).once
599
+ flexmock(@client).should_receive(:sleep).with(4).once
600
+ @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 1)
601
+ end
602
+
603
+ it "changes sleep interval for successive retries" do
604
+ @log.should_receive(:error).with(/Retrying type request/).once
605
+ flexmock(@client).should_receive(:sleep).with(12).once
606
+ @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 2)
607
+ end
608
+
609
+ it "does not retry more than configured number of retry intervals" do
610
+ lambda { @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 4) }.should \
611
+ raise_error(RightScale::Exceptions::ConnectivityFailure, "Server not responding after 4 attempts")
612
+ end
613
+
614
+ it "sets state to :disconnected and raises connectivity error if retry timed out" do
615
+ @client.init(:test, @auth_client, @options.merge(:retry_enabled => true, :retry_timeout => 10))
616
+ # Need to shut off reconnect, otherwise since timers are always yielding,
617
+ # setting state to :disconnected sets it to :connected
618
+ flexmock(@client).should_receive(:reconnect).once
619
+ lambda { @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 3) }.should \
620
+ raise_error(RightScale::Exceptions::ConnectivityFailure, "Server not responding after 3 attempts")
621
+ @client.state.should == :disconnected
622
+ end
623
+
624
+ it "sets state to :disconnected and raises connectivity error if retry disabled" do
625
+ @client.init(:test, @auth_client, @options.merge(:retry_enabled => false))
626
+ # Need to shut off reconnect, otherwise since timers are always yielding,
627
+ # setting state to :disconnected sets it to :connected
628
+ flexmock(@client).should_receive(:reconnect).once
629
+ lambda { @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 1) }.should \
630
+ raise_error(RightScale::Exceptions::ConnectivityFailure, "Server not responding")
631
+ @client.state.should == :disconnected
632
+ end
633
+ end
634
+ end
635
+ end