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,594 @@
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', 'router_client'))
26
+
27
+ describe RightScale::RouterClient 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
+ @timer = flexmock("timer", :cancel => true, :interval= => 0).by_default
36
+ flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).by_default
37
+ @http_client = flexmock("http client", :get => true, :check_health => true).by_default
38
+ flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).by_default
39
+ @websocket = WebSocketClientMock.new
40
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).by_default
41
+ @auth_header = {"Authorization" => "Bearer <session>"}
42
+ @url = "http://test.com"
43
+ @ws_url = "ws://test.com"
44
+ @auth_client = AuthClientMock.new(@url, @auth_header, :authorized)
45
+ @routing_keys = nil
46
+ @options = {}
47
+ @client = RightScale::RouterClient.new(@auth_client, @options)
48
+ @version = RightScale::AgentConfig.protocol_version
49
+ @event = {:uuid => "uuid", :type => "Push", :path => "/foo/bar", :from => "rs-agent-1-1", :data => {}, :version => @version}
50
+ end
51
+
52
+ context :initialize do
53
+ it "initializes options" do
54
+ @options = {
55
+ :open_timeout => 1,
56
+ :request_timeout => 2,
57
+ :listen_timeout => 3,
58
+ :retry_timeout => 4,
59
+ :retry_intervals => [1, 2, 3],
60
+ :reconnect_interval => 5 }
61
+ @client = RightScale::RouterClient.new(@auth_client, @options)
62
+ options = @client.instance_variable_get(:@options)
63
+ options[:server_name] = "RightNet"
64
+ options[:api_version] = "2.0"
65
+ options[:open_timeout] = 1
66
+ options[:request_timeout] = 2
67
+ options[:listen_timeout] = 3
68
+ options[:retry_timeout] = 4
69
+ options[:retry_intervals] = [1, 2, 3]
70
+ options[:reconnect_interval] = 5
71
+ end
72
+
73
+ it "initializes options to defaults if no value specified" do
74
+ options = @client.instance_variable_get(:@options)
75
+ options[:listen_timeout].should == 60
76
+ end
77
+ end
78
+
79
+ context "requests" do
80
+
81
+ before(:each) do
82
+ @type = "/foo/bar"
83
+ @action = "bar"
84
+ @payload = {:some => "data"}
85
+ @target = "rs-agent-2-2"
86
+ @token = "random token"
87
+ @params = {
88
+ :type => @type,
89
+ :payload => @payload,
90
+ :target => @target }
91
+ end
92
+
93
+ context :push do
94
+ it "makes post request to router" do
95
+ flexmock(@client).should_receive(:make_request).with(:post, "/push", @params, @action, @token).
96
+ and_return(nil).once
97
+ @client.push(@type, @payload, @target, @token).should be_nil
98
+ end
99
+
100
+ it "does not require token" do
101
+ flexmock(@client).should_receive(:make_request).with(:post, "/push", @params, @action, nil).
102
+ and_return(nil).once
103
+ @client.push(@type, @payload, @target).should be_nil
104
+ end
105
+ end
106
+
107
+ context :request do
108
+ it "makes post request to router" do
109
+ flexmock(@client).should_receive(:make_request).with(:post, "/request", @params, @action, @token).
110
+ and_return(nil).once
111
+ @client.request(@type, @payload, @target, @token)
112
+ end
113
+
114
+ it "does not require token" do
115
+ flexmock(@client).should_receive(:make_request).with(:post, "/request", @params, @action, nil).
116
+ and_return(nil).once
117
+ @client.request(@type, @payload, @target).should be_nil
118
+ end
119
+ end
120
+ end
121
+
122
+ context "events" do
123
+
124
+ before(:each) do
125
+ @later = Time.at(@now = Time.now)
126
+ @tick = 30
127
+ flexmock(Time).should_receive(:now).and_return { @later += @tick }
128
+ end
129
+
130
+ context :notify do
131
+ before(:each) do
132
+ @routing_keys = ["key"]
133
+ @params = {
134
+ :event => @event,
135
+ :routing_keys => @routing_keys }
136
+ end
137
+
138
+ it "sends using websocket if available" do
139
+ @client.send(:connect, @routing_keys) { |_| }
140
+ @client.notify(@event, @routing_keys).should be_true
141
+ @websocket.sent.should == JSON.dump(@params)
142
+ end
143
+
144
+ it "makes post request by default" do
145
+ flexmock(@client).should_receive(:make_request).with(:post, "/notify", @params, "notify", "uuid",
146
+ {:filter_params => ["event"]}).once
147
+ @client.notify(@event, @routing_keys).should be_true
148
+ end
149
+ end
150
+
151
+ context :listen do
152
+ it "raises if block missing" do
153
+ lambda { @client.listen(@routing_keys) }.should raise_error(ArgumentError, "Block missing")
154
+ end
155
+
156
+ it "loops forever until closed" do
157
+ @client.close
158
+ @client.listen(@routing_keys) { |_| }.should be_true
159
+ end
160
+
161
+ it "loops forever until closing" do
162
+ @client.close(:receive)
163
+ @client.listen(@routing_keys) { |_| }.should be_true
164
+ end
165
+
166
+ it "sleeps if websocket already exists" do
167
+ @client.send(:connect, @routing_keys) { |_| }
168
+ flexmock(@client).should_receive(:sleep).with(5).and_return { @client.close }.once
169
+ @client.listen(@routing_keys) { |_| }
170
+ @client.instance_variable_get(:@connect_interval).should == 30
171
+ end
172
+
173
+ it "tries to create websocket if time to" do
174
+ @client.instance_variable_get(:@websocket).should be_nil
175
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
176
+ flexmock(@client).should_receive(:sleep).and_return { @client.close }
177
+ @client.listen(@routing_keys) { |_| }
178
+ end
179
+
180
+ it "does not try long-polling if websocket connect attempt indicates not to" do
181
+ @client.instance_variable_get(:@websocket).should be_nil
182
+ flexmock(@client).should_receive(:try_connect).and_return { @client.close; true }
183
+ flexmock(@client).should_receive(:try_long_poll).never
184
+ @client.listen(@routing_keys) { |_| }
185
+ end
186
+
187
+ it "tries long-polling if could not create websocket" do
188
+ @client.instance_variable_get(:@websocket).should be_nil
189
+ flexmock(@client).should_receive(:retry_connect?).and_return(false)
190
+ flexmock(@client).should_receive(:long_poll).and_return { @client.close; ["uuid"] }.once
191
+ @client.listen(@routing_keys) { |_| }.should be_true
192
+ end
193
+ end
194
+
195
+ context :close do
196
+ it "closes websocket" do
197
+ @client.send(:connect, nil) { |_| }
198
+ @client.close
199
+ @websocket.closed.should be_true
200
+ @websocket.code.should == 1001
201
+ end
202
+ end
203
+
204
+ context :retry_connect? do
205
+ before(:each) do
206
+ @client.instance_variable_set(:@last_connect_time, @now)
207
+ @client.instance_variable_set(:@connect_interval, 30)
208
+ end
209
+
210
+ it "requires websocket to be enabled" do
211
+ @client = RightScale::RouterClient.new(@auth_client, :long_polling_only => true)
212
+ @client.send(:retry_connect?).should be_false
213
+ end
214
+
215
+ it "requires there be no existing websocket connection" do
216
+ @client.instance_variable_set(:@websocket, @websocket)
217
+ @client.send(:retry_connect?).should be_false
218
+ end
219
+
220
+ context "when no existing websocket" do
221
+ before(:each) do
222
+ @client.instance_variable_get(:@websocket).should be_nil
223
+ end
224
+
225
+ it "allows retry if enough time has elapsed" do
226
+ @tick = 1
227
+ @client.instance_variable_set(:@last_connect_time, @now - 29)
228
+ @client.send(:retry_connect?).should be_false
229
+ @client.instance_variable_set(:@last_connect_time, @now - 30)
230
+ @client.send(:retry_connect?).should be_true
231
+ end
232
+
233
+ [RightScale::RouterClient::NORMAL_CLOSE, RightScale::RouterClient::SHUTDOWN_CLOSE].each do |code|
234
+ it "allows retry if previous close code is #{code}" do
235
+ @client.instance_variable_set(:@close_code, code)
236
+ @client.instance_variable_set(:@connect_interval, 300)
237
+ @client.send(:retry_connect?).should be_true
238
+ end
239
+ end
240
+
241
+ [502, 503].each do |code|
242
+ it "allows retry if previous close has reason with code #{code} indicating router inaccessible" do
243
+ @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
244
+ @client.instance_variable_set(:@close_reason, "Unexpected response code: #{code}")
245
+ @client.instance_variable_set(:@connect_interval, 300)
246
+ @client.send(:retry_connect?).should be_true
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ context :try_connect do
253
+ before(:each) do
254
+ @handler = lambda { |_| }
255
+ @client.instance_variable_get(:@websocket).should be_nil
256
+ @client.instance_variable_set(:@connect_interval, 30)
257
+ @client.instance_variable_set(:@reconnect_interval, 2)
258
+ end
259
+
260
+ it "makes websocket connect request" do
261
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
262
+ flexmock(@client).should_receive(:sleep).and_return { @client.close }
263
+ @client.send(:try_connect, @routing_keys, &@handler)
264
+ end
265
+
266
+ it "periodically checks whether websocket creation was really successful" do
267
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
268
+ flexmock(@client).should_receive(:sleep).with(1).times(3).ordered
269
+ flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil) }.once.ordered
270
+ @client.send(:try_connect, @routing_keys, &@handler)
271
+ end
272
+
273
+ it "sleeps if websocket creation failed because router not responding" do
274
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
275
+ flexmock(@client).should_receive(:sleep).with(1).and_return do
276
+ @client.instance_variable_set(:@websocket, nil)
277
+ @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
278
+ @client.instance_variable_set(:@close_reason, "Unexpected response code: 502")
279
+ end.once
280
+ flexmock(@client).should_receive(:sleep).with(4).and_return { @client.close }.once
281
+ @client.send(:try_connect, @routing_keys, &@handler)
282
+ @client.instance_variable_get(:@reconnect_interval).should == 4
283
+ @client.instance_variable_get(:@connect_interval).should == 30
284
+ end
285
+
286
+ it "adjusts connect interval if websocket creation was unsuccessful" do
287
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
288
+ flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil); @client.close }.once
289
+ @client.send(:try_connect, @routing_keys, &@handler)
290
+ @client.instance_variable_get(:@connect_interval).should == 60
291
+ @client.instance_variable_get(:@reconnect_interval).should == 2
292
+ end
293
+
294
+ it "loops instead of long-polling if websocket creation was unsuccessful" do
295
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
296
+ flexmock(@client).should_receive(:sleep).and_return { @client.instance_variable_set(:@websocket, nil); @client.close }
297
+ flexmock(@client).should_receive(:long_poll).never
298
+ @client.send(:try_connect, @routing_keys, &@handler)
299
+ end
300
+
301
+ it "sleeps after successfully creating websocket" do
302
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket)
303
+ flexmock(@client).should_receive(:sleep).with(1).times(4).ordered
304
+ flexmock(@client).should_receive(:sleep).with(1).and_return { @client.close }.once.ordered
305
+ @client.send(:try_connect, @routing_keys, &@handler)
306
+ end
307
+
308
+ it "adjusts connect interval if websocket creation fails" do
309
+ @log.should_receive(:error).with("Failed creating WebSocket", StandardError).once
310
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_raise(StandardError).once
311
+ flexmock(@client).should_receive(:sleep).never
312
+ @client.send(:try_connect, @routing_keys, &@handler)
313
+ @client.instance_variable_get(:@connect_interval).should == 60
314
+ end
315
+ end
316
+
317
+ context :connect do
318
+ it "raises if block missing" do
319
+ lambda { @client.send(:connect, @routing_keys) }.should raise_error(ArgumentError, "Block missing")
320
+ end
321
+
322
+ context "when creating connection" do
323
+ it "connects to router" do
324
+ flexmock(Faye::WebSocket::Client).should_receive(:new).with(@ws_url + "/connect", nil, Hash).and_return(@websocket).once
325
+ @client.send(:connect, @routing_keys) { |_| }
326
+ end
327
+
328
+ it "chooses scheme based on scheme in router URL" do
329
+ @url = "https://test.com"
330
+ @ws_url = "wss://test.com"
331
+ @auth_client = AuthClientMock.new(@url, @auth_header, :authorized)
332
+ @client = RightScale::RouterClient.new(@auth_client, @options)
333
+ flexmock(Faye::WebSocket::Client).should_receive(:new).with(@ws_url + "/connect", nil, Hash).and_return(@websocket).once
334
+ @client.send(:connect, @routing_keys) { |_| }
335
+ end
336
+
337
+ it "uses headers containing only API version and authorization" do
338
+ headers = @auth_header.merge("X-API-Version" => "2.0")
339
+ flexmock(Faye::WebSocket::Client).should_receive(:new).with(String, nil, hsh(:headers => headers)).and_return(@websocket).once
340
+ @client.send(:connect, @routing_keys) { |_| }
341
+ end
342
+
343
+ it "enables ping" do
344
+ flexmock(Faye::WebSocket::Client).should_receive(:new).with(String, nil, hsh(:ping => 60)).and_return(@websocket).once
345
+ @client.send(:connect, @routing_keys) { |_| }
346
+ end
347
+
348
+ it "adds routing keys as query parameters" do
349
+ url = @ws_url + "/connect" + "?routing_keys[]=a%3Ab%3Dc"
350
+ flexmock(Faye::WebSocket::Client).should_receive(:new).with(url, nil, Hash).and_return(@websocket).once
351
+ @client.send(:connect, ["a:b=c"]) { |_| }
352
+ end
353
+
354
+ it "returns websocket" do
355
+ flexmock(Faye::WebSocket::Client).should_receive(:new).and_return(@websocket).once
356
+ @client.send(:connect, @routing_keys) { |_| }.should == @websocket
357
+ @client.instance_variable_get(:@websocket).should == @websocket
358
+ end
359
+ end
360
+
361
+ context "when message received" do
362
+ before(:each) do
363
+ @json_event = JSON.dump(@event)
364
+ @json_ack = JSON.dump({:ack => "uuid"})
365
+ end
366
+
367
+ it "presents JSON-decoded event to the specified handler" do
368
+ event = nil
369
+ @client.send(:connect, @routing_keys) { |e| event = e }
370
+ @websocket.onmessage(@json_event)
371
+ event.should == @event
372
+ end
373
+
374
+ it "logs event" do
375
+ @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
376
+ @log.should_receive(:info).with("Received EVENT <uuid> Push /foo/bar from rs-agent-1-1").once.ordered
377
+ @log.should_receive(:info).with("Sending EVENT <uuid> Push /foo/bar to rs-agent-1-1").once.ordered
378
+ event = nil
379
+ @client.send(:connect, @routing_keys) { |e| event = e }
380
+ @websocket.onmessage(@json_event)
381
+ event.should == @event
382
+ end
383
+
384
+ it "acknowledges event" do
385
+ @client.send(:connect, @routing_keys) { |_| nil }
386
+ @websocket.onmessage(@json_event)
387
+ @websocket.sent.should == @json_ack
388
+ end
389
+
390
+ it "sends event response using websocket" do
391
+ result = {:uuid => "uuid2", :type => "Result", :from => "rs-agent-2-2", :data => {}, :version => @version}
392
+ @client.send(:connect, @routing_keys) { |_| result }
393
+ @websocket.onmessage(@json_event)
394
+ @websocket.sent.should == [@json_ack, JSON.dump({:event => result, :routing_keys => ["rs-agent-1-1"]})]
395
+ end
396
+
397
+ it "only sends non-nil responses" do
398
+ @client.send(:connect, @routing_keys) { |_| nil }
399
+ @websocket.onmessage(@json_event)
400
+ @websocket.sent.should == @json_ack
401
+ end
402
+
403
+ it "logs failures" do
404
+ @log.should_receive(:error).with("Failed handling WebSocket event", StandardError, :trace).once
405
+ request = RightScale::Request.new("/foo/bar", "payload")
406
+ @client.send(:connect, @routing_keys) { |_| raise StandardError, "bad event" }
407
+ @websocket.onmessage(JSON.dump(request))
408
+ end
409
+ end
410
+
411
+ context "on close" do
412
+ it "logs info" do
413
+ @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
414
+ @log.should_receive(:info).with("WebSocket closed (1000)").once.ordered
415
+ @client.send(:connect, @routing_keys) { |_| }
416
+ @websocket.onclose(1000)
417
+ @client.instance_variable_get(:@websocket).should be_nil
418
+ end
419
+
420
+ it "logged info includes reason if available" do
421
+ @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
422
+ @log.should_receive(:info).with("WebSocket closed (1001: Going Away)").once.ordered
423
+ @client.send(:connect, @routing_keys) { |_| }
424
+ @websocket.onclose(1001, "Going Away")
425
+ end
426
+
427
+ it "logs unexpected exceptions" do
428
+ @log.should_receive(:info).with("Creating WebSocket connection to ws://test.com/connect").once.ordered
429
+ @log.should_receive(:info).and_raise(RuntimeError).once.ordered
430
+ @log.should_receive(:error).with("Failed closing WebSocket", RuntimeError, :trace).once
431
+ @client.send(:connect, @routing_keys) { |_| }
432
+ @websocket.onclose(1000)
433
+ end
434
+ end
435
+
436
+ context "on error" do
437
+ it "logs error" do
438
+ @log.should_receive(:error).with("WebSocket error (Protocol Error)")
439
+ @client.send(:connect, @routing_keys) { |_| }
440
+ @websocket.onerror("Protocol Error")
441
+ end
442
+
443
+ it "does not log if there is no error data" do
444
+ @log.should_receive(:error).never
445
+ @client.send(:connect, @routing_keys) { |_| }
446
+ @websocket.onerror(nil)
447
+ end
448
+ end
449
+ end
450
+
451
+ context :try_long_poll do
452
+ before(:each) do
453
+ @uuids = ["uuid"]
454
+ @handler = lambda { |_| }
455
+ @client.instance_variable_set(:@connect_interval, 30)
456
+ @client.instance_variable_set(:@reconnect_interval, 2)
457
+ end
458
+
459
+ it "makes long-polling request" do
460
+ flexmock(@client).should_receive(:long_poll).with(@routing_keys, @uuids, @handler).and_return([]).once
461
+ @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should == []
462
+ end
463
+
464
+ it "returns UUIDs of events received" do
465
+ flexmock(@client).should_receive(:long_poll).with(@routing_keys, [], @handler).and_return { @uuids }.once
466
+ @client.send(:try_long_poll, @routing_keys, [], &@handler).should == @uuids
467
+ end
468
+
469
+ it "sleeps if there is a long-polling failure" do
470
+ @log.should_receive(:error).with("Failed long-polling", StandardError, :trace).once
471
+ flexmock(@client).should_receive(:long_poll).and_raise(StandardError).once
472
+ flexmock(@client).should_receive(:sleep).with(4).once
473
+ @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should be_nil
474
+ end
475
+
476
+ [RightScale::Exceptions::Unauthorized,
477
+ RightScale::Exceptions::ConnectivityFailure,
478
+ RightScale::Exceptions::RetryableError].each do |e|
479
+ it "does not trace #{e} exceptions" do
480
+ @log.should_receive(:error).with("Failed long-polling", e, :no_trace).once
481
+ flexmock(@client).should_receive(:long_poll).and_raise(e, "failed").once
482
+ flexmock(@client).should_receive(:sleep).with(4).once
483
+ @client.send(:try_long_poll, @routing_keys, @uuids, &@handler).should be_nil
484
+ end
485
+ end
486
+ end
487
+
488
+ context :long_poll do
489
+ before(:each) do
490
+ @ack = []
491
+ end
492
+
493
+ it "raises if block missing" do
494
+ lambda { @client.send(:long_poll, @routing_keys, @ack) }.should raise_error(ArgumentError, "Block missing")
495
+ end
496
+
497
+ it "makes listen request to router" do
498
+ flexmock(@client).should_receive(:make_request).with(:get, "/listen",
499
+ on { |a| a[:wait_time].should == 55 && !a.key?(:routing_keys) &&
500
+ a[:timestamp] == @later.to_f }, "listen", nil, Hash).and_return([@event]).once
501
+ @client.send(:long_poll, @routing_keys, @ack) { |_| }
502
+ end
503
+
504
+ it "uses listen timeout for request" do
505
+ flexmock(@client).should_receive(:make_request).with(:get, "/listen", Hash, "listen", nil,
506
+ {:request_timeout => 60, :log_level => :debug}).and_return([@event]).once
507
+ @client.send(:long_poll, @routing_keys, @ack) { |_| }
508
+ end
509
+
510
+ it "logs event" do
511
+ @log.should_receive(:info).with("Received EVENT <uuid> Push /foo/bar from rs-agent-1-1").once
512
+ flexmock(@client).should_receive(:make_request).and_return([@event])
513
+ @client.send(:long_poll, @routing_keys, @ack) { |_| }
514
+ end
515
+
516
+ it "presents event to handler" do
517
+ flexmock(@client).should_receive(:make_request).and_return([@event])
518
+ event = nil
519
+ @client.send(:long_poll, @routing_keys, @ack) { |e| event = e }
520
+ event.should == @event
521
+ end
522
+
523
+ it "handles event keys that are strings" do
524
+ event = {"uuid" => "uuid", "type" => "Push", "path" => "/foo/bar", "from" => "rs-agent-1-1", "data" => {}, "version" => @version}
525
+ @log.should_receive(:info).with("Received EVENT <uuid> Push /foo/bar from rs-agent-1-1").once
526
+ flexmock(@client).should_receive(:make_request).and_return([event])
527
+ event = nil
528
+ @client.send(:long_poll, @routing_keys, @ack) { |e| event = e }
529
+ event.should == @event
530
+ end
531
+
532
+ it "does nothing if no events are returned" do
533
+ flexmock(@client).should_receive(:make_request).and_return(nil)
534
+ event = nil
535
+ @client.send(:long_poll, @routing_keys, @ack) { |e| event = e }
536
+ event.should be_nil
537
+ end
538
+
539
+ it "returns event UUIDs" do
540
+ flexmock(@client).should_receive(:make_request).and_return([@event])
541
+ @client.send(:long_poll, @routing_keys, @ack) { |_| }.should == ["uuid"]
542
+ end
543
+
544
+ it "returns nil if no events are received" do
545
+ flexmock(@client).should_receive(:make_request).and_return(nil)
546
+ @client.send(:long_poll, @routing_keys, @ack) { |_| }.should be_nil
547
+ end
548
+ end
549
+
550
+ context :backoff_connect_interval do
551
+ it "backs off exponentially" do
552
+ @client.instance_variable_set(:@connect_interval, 30)
553
+ @client.send(:backoff_connect_interval).should == 60
554
+ @client.send(:backoff_connect_interval).should == 120
555
+ end
556
+
557
+ it "limits backoff" do
558
+ @client.instance_variable_set(:@connect_interval, 30)
559
+ 12.times { @client.send(:backoff_connect_interval) }
560
+ @client.instance_variable_get(:@connect_interval).should == RightScale::RouterClient::MAX_CONNECT_INTERVAL
561
+ end
562
+ end
563
+
564
+ context :backoff_reconnect_interval do
565
+ it "backs off exponentially" do
566
+ @client.instance_variable_set(:@reconnect_interval, 2)
567
+ @client.send(:backoff_reconnect_interval).should == 4
568
+ @client.send(:backoff_reconnect_interval).should == 8
569
+ end
570
+
571
+ it "limits backoff" do
572
+ @client.instance_variable_set(:@reconnect_interval, 2)
573
+ 6.times { @client.send(:backoff_reconnect_interval) }
574
+ @client.instance_variable_get(:@reconnect_interval).should == RightScale::RouterClient::MAX_RECONNECT_INTERVAL
575
+ end
576
+ end
577
+
578
+ context :router_not_responding? do
579
+ [502, 503].each do |code|
580
+ it "declares not responding if have close reason code #{code} indicating router inaccessible" do
581
+ @client.instance_variable_set(:@close_code, RightScale::RouterClient::PROTOCOL_ERROR_CLOSE)
582
+ @client.instance_variable_set(:@close_reason, "Unexpected response code: #{code}")
583
+ @client.send(:router_not_responding?).should be_true
584
+ end
585
+ end
586
+
587
+ it "does not declare not responding for other close codes" do
588
+ @client.instance_variable_set(:@close_code, RightScale::RouterClient::UNEXPECTED_ERROR_CLOSE)
589
+ @client.instance_variable_set(:@close_reason, "Unexpected response code: 502")
590
+ @client.send(:router_not_responding?).should be_false
591
+ end
592
+ end
593
+ end
594
+ end