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.
- data/LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +113 -0
- data/lib/right_agent.rb +59 -0
- data/lib/right_agent/actor.rb +182 -0
- data/lib/right_agent/actor_registry.rb +76 -0
- data/lib/right_agent/actors/agent_manager.rb +232 -0
- data/lib/right_agent/agent.rb +1149 -0
- data/lib/right_agent/agent_config.rb +480 -0
- data/lib/right_agent/agent_identity.rb +210 -0
- data/lib/right_agent/agent_tag_manager.rb +237 -0
- data/lib/right_agent/audit_formatter.rb +107 -0
- data/lib/right_agent/clients.rb +31 -0
- data/lib/right_agent/clients/api_client.rb +383 -0
- data/lib/right_agent/clients/auth_client.rb +247 -0
- data/lib/right_agent/clients/balanced_http_client.rb +369 -0
- data/lib/right_agent/clients/base_retry_client.rb +495 -0
- data/lib/right_agent/clients/right_http_client.rb +279 -0
- data/lib/right_agent/clients/router_client.rb +493 -0
- data/lib/right_agent/command.rb +30 -0
- data/lib/right_agent/command/agent_manager_commands.rb +150 -0
- data/lib/right_agent/command/command_client.rb +136 -0
- data/lib/right_agent/command/command_constants.rb +33 -0
- data/lib/right_agent/command/command_io.rb +126 -0
- data/lib/right_agent/command/command_parser.rb +87 -0
- data/lib/right_agent/command/command_runner.rb +118 -0
- data/lib/right_agent/command/command_serializer.rb +63 -0
- data/lib/right_agent/connectivity_checker.rb +179 -0
- data/lib/right_agent/console.rb +65 -0
- data/lib/right_agent/core_payload_types.rb +44 -0
- data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
- data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
- data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
- data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
- data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
- data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
- data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
- data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
- data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
- data/lib/right_agent/core_payload_types/login_user.rb +79 -0
- data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
- data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
- data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
- data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
- data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
- data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
- data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
- data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
- data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
- data/lib/right_agent/daemonize.rb +35 -0
- data/lib/right_agent/dispatched_cache.rb +109 -0
- data/lib/right_agent/dispatcher.rb +272 -0
- data/lib/right_agent/enrollment_result.rb +221 -0
- data/lib/right_agent/exceptions.rb +87 -0
- data/lib/right_agent/history.rb +145 -0
- data/lib/right_agent/log.rb +460 -0
- data/lib/right_agent/minimal.rb +46 -0
- data/lib/right_agent/monkey_patches.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
- data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
- data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
- data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
- data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
- data/lib/right_agent/multiplexer.rb +102 -0
- data/lib/right_agent/offline_handler.rb +270 -0
- data/lib/right_agent/operation_result.rb +300 -0
- data/lib/right_agent/packets.rb +673 -0
- data/lib/right_agent/payload_formatter.rb +104 -0
- data/lib/right_agent/pending_requests.rb +128 -0
- data/lib/right_agent/pid_file.rb +159 -0
- data/lib/right_agent/platform.rb +770 -0
- data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
- data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
- data/lib/right_agent/platform/unix/platform.rb +226 -0
- data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
- data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
- data/lib/right_agent/platform/windows/platform.rb +1808 -0
- data/lib/right_agent/protocol_version_mixin.rb +69 -0
- data/lib/right_agent/retryable_request.rb +195 -0
- data/lib/right_agent/scripts/agent_controller.rb +543 -0
- data/lib/right_agent/scripts/agent_deployer.rb +400 -0
- data/lib/right_agent/scripts/common_parser.rb +160 -0
- data/lib/right_agent/scripts/log_level_manager.rb +192 -0
- data/lib/right_agent/scripts/stats_manager.rb +268 -0
- data/lib/right_agent/scripts/usage.rb +58 -0
- data/lib/right_agent/secure_identity.rb +92 -0
- data/lib/right_agent/security.rb +32 -0
- data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
- data/lib/right_agent/security/certificate.rb +102 -0
- data/lib/right_agent/security/certificate_cache.rb +89 -0
- data/lib/right_agent/security/distinguished_name.rb +56 -0
- data/lib/right_agent/security/encrypted_document.rb +83 -0
- data/lib/right_agent/security/rsa_key_pair.rb +76 -0
- data/lib/right_agent/security/signature.rb +86 -0
- data/lib/right_agent/security/static_certificate_store.rb +85 -0
- data/lib/right_agent/sender.rb +792 -0
- data/lib/right_agent/serialize.rb +29 -0
- data/lib/right_agent/serialize/message_pack.rb +107 -0
- data/lib/right_agent/serialize/secure_serializer.rb +151 -0
- data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
- data/lib/right_agent/serialize/serializable.rb +151 -0
- data/lib/right_agent/serialize/serializer.rb +159 -0
- data/lib/right_agent/subprocess.rb +38 -0
- data/lib/right_agent/tracer.rb +124 -0
- data/right_agent.gemspec +101 -0
- data/spec/actor_registry_spec.rb +80 -0
- data/spec/actor_spec.rb +162 -0
- data/spec/agent_config_spec.rb +235 -0
- data/spec/agent_identity_spec.rb +78 -0
- data/spec/agent_spec.rb +734 -0
- data/spec/agent_tag_manager_spec.rb +319 -0
- data/spec/clients/api_client_spec.rb +423 -0
- data/spec/clients/auth_client_spec.rb +272 -0
- data/spec/clients/balanced_http_client_spec.rb +576 -0
- data/spec/clients/base_retry_client_spec.rb +635 -0
- data/spec/clients/router_client_spec.rb +594 -0
- data/spec/clients/spec_helper.rb +111 -0
- data/spec/command/agent_manager_commands_spec.rb +51 -0
- data/spec/command/command_io_spec.rb +93 -0
- data/spec/command/command_parser_spec.rb +79 -0
- data/spec/command/command_runner_spec.rb +107 -0
- data/spec/command/command_serializer_spec.rb +51 -0
- data/spec/connectivity_checker_spec.rb +83 -0
- data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
- data/spec/core_payload_types/dev_repository_spec.rb +33 -0
- data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
- data/spec/core_payload_types/login_user_spec.rb +102 -0
- data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
- data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
- data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
- data/spec/core_payload_types/spec_helper.rb +23 -0
- data/spec/dispatched_cache_spec.rb +136 -0
- data/spec/dispatcher_spec.rb +324 -0
- data/spec/enrollment_result_spec.rb +53 -0
- data/spec/history_spec.rb +246 -0
- data/spec/log_spec.rb +192 -0
- data/spec/monkey_patches/eventmachine_spec.rb +62 -0
- data/spec/multiplexer_spec.rb +48 -0
- data/spec/offline_handler_spec.rb +340 -0
- data/spec/operation_result_spec.rb +208 -0
- data/spec/packets_spec.rb +461 -0
- data/spec/pending_requests_spec.rb +136 -0
- data/spec/platform/spec_helper.rb +216 -0
- data/spec/platform/unix/darwin/platform_spec.rb +181 -0
- data/spec/platform/unix/linux/platform_spec.rb +540 -0
- data/spec/platform/unix/spec_helper.rb +149 -0
- data/spec/platform/windows/mingw/platform_spec.rb +222 -0
- data/spec/platform/windows/mswin/platform_spec.rb +259 -0
- data/spec/platform/windows/spec_helper.rb +720 -0
- data/spec/retryable_request_spec.rb +306 -0
- data/spec/secure_identity_spec.rb +50 -0
- data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
- data/spec/security/certificate_cache_spec.rb +71 -0
- data/spec/security/certificate_spec.rb +49 -0
- data/spec/security/distinguished_name_spec.rb +46 -0
- data/spec/security/encrypted_document_spec.rb +55 -0
- data/spec/security/rsa_key_pair_spec.rb +55 -0
- data/spec/security/signature_spec.rb +66 -0
- data/spec/security/static_certificate_store_spec.rb +58 -0
- data/spec/sender_spec.rb +1045 -0
- data/spec/serialize/message_pack_spec.rb +131 -0
- data/spec/serialize/secure_serializer_spec.rb +132 -0
- data/spec/serialize/serializable_spec.rb +90 -0
- data/spec/serialize/serializer_spec.rb +197 -0
- data/spec/spec.opts +2 -0
- data/spec/spec.win32.opts +1 -0
- data/spec/spec_helper.rb +130 -0
- data/spec/tracer_spec.rb +114 -0
- metadata +447 -0
@@ -0,0 +1,272 @@
|
|
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', 'auth_client'))
|
26
|
+
|
27
|
+
class AuthClientTest < RightScale::AuthClient
|
28
|
+
def initialize(options = {})
|
29
|
+
@identity = options[:identity]
|
30
|
+
@api_url = options[:api_url]
|
31
|
+
@router_url = options[:router_url]
|
32
|
+
@account_id = options[:account_id]
|
33
|
+
@mode = options[:mode]
|
34
|
+
@access_token = options[:access_token]
|
35
|
+
@state = :pending
|
36
|
+
@status_callbacks = []
|
37
|
+
reset_stats
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe RightScale::AuthClient do
|
42
|
+
|
43
|
+
include FlexMock::ArgumentTypes
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
@log = flexmock(RightScale::Log)
|
47
|
+
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
48
|
+
@log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
49
|
+
@identity = "rs-agent-1-1"
|
50
|
+
@api_url = "http://api.com"
|
51
|
+
@router_url = "http://router.com"
|
52
|
+
@options = {
|
53
|
+
:identity => @identity,
|
54
|
+
:api_url => @api_url,
|
55
|
+
:router_url => @router_url,
|
56
|
+
:account_id => 123,
|
57
|
+
:mode => :http,
|
58
|
+
:access_token => "<test access token>" }
|
59
|
+
@client = AuthClientTest.new(@options)
|
60
|
+
end
|
61
|
+
|
62
|
+
context :initialize do
|
63
|
+
it "raises if abstract class" do
|
64
|
+
lambda { RightScale::AuthClient.new }.should raise_error(NotImplementedError, "RightScale::AuthClient is an abstract class")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "does not raise for derived class" do
|
68
|
+
AuthClientTest.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context :identity do
|
73
|
+
it "returns identity" do
|
74
|
+
@client.identity.should == @identity
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context :headers do
|
79
|
+
it "raises if not authorized" do
|
80
|
+
lambda { @client.headers }.should raise_error(RightScale::Exceptions::Unauthorized)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns headers" do
|
84
|
+
@client.send(:state=, :authorized)
|
85
|
+
@client.headers.should == {"Authorization" => "Bearer <test access token>"}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context :auth_header do
|
90
|
+
it "raises if not authorized" do
|
91
|
+
lambda { @client.auth_header }.should raise_error(RightScale::Exceptions::Unauthorized)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns authorization header" do
|
95
|
+
@client.send(:state=, :authorized)
|
96
|
+
@client.auth_header.should == {"Authorization" => "Bearer <test access token>"}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context :account_id do
|
101
|
+
it "raises if not authorized" do
|
102
|
+
lambda { @client.account_id }.should raise_error(RightScale::Exceptions::Unauthorized)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "returns auth header" do
|
106
|
+
@client.send(:state=, :authorized)
|
107
|
+
@client.account_id.should == 123
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context :api_url do
|
112
|
+
it "raises if not authorized" do
|
113
|
+
lambda { @client.api_url }.should raise_error(RightScale::Exceptions::Unauthorized)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns auth header" do
|
117
|
+
@client.send(:state=, :authorized)
|
118
|
+
@client.api_url.should == @api_url
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context :router_url do
|
123
|
+
it "raises if not authorized" do
|
124
|
+
lambda { @client.router_url }.should raise_error(RightScale::Exceptions::Unauthorized)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns auth header" do
|
128
|
+
@client.send(:state=, :authorized)
|
129
|
+
@client.router_url.should == @router_url
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context :mode do
|
134
|
+
it "returns mode" do
|
135
|
+
@client.mode.should == :http
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context :expired do
|
140
|
+
it "logs that authorization expired" do
|
141
|
+
@log.should_receive(:info).with(/Renewing authorization/).once
|
142
|
+
@client.send(:state=, :authorized)
|
143
|
+
@client.expired.should be_true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "sets state to :expired" do
|
147
|
+
@client.send(:state=, :authorized)
|
148
|
+
@client.expired.should be_true
|
149
|
+
@client.state.should == :expired
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should renew authorization" do
|
153
|
+
@client.send(:state=, :authorized)
|
154
|
+
flexmock(@client).should_receive(:renew_authorization).once
|
155
|
+
@client.expired.should be_true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context :redirect do
|
160
|
+
it "handles redirect" do
|
161
|
+
@client.redirect("location").should be_true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context :close do
|
166
|
+
it "should set state to :closed" do
|
167
|
+
@client.close.should be_true
|
168
|
+
@client.state.should == :closed
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context :status do
|
173
|
+
it "stores callback" do
|
174
|
+
callback = lambda { |_, _| }
|
175
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
176
|
+
@client.status(&callback)
|
177
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 1
|
178
|
+
@client.instance_variable_get(:@status_callbacks)[0].should == callback
|
179
|
+
end
|
180
|
+
|
181
|
+
it "treats callback as optional" do
|
182
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
183
|
+
@client.status
|
184
|
+
@client.instance_variable_get(:@status_callbacks).size.should == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
it "returns current state" do
|
188
|
+
@client.status.should == :pending
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context :check_authorized do
|
193
|
+
it "raises retryable error if state is :expired" do
|
194
|
+
@client.send(:state=, :authorized)
|
195
|
+
@client.send(:expired)
|
196
|
+
lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::RetryableError, "Authorization expired")
|
197
|
+
end
|
198
|
+
|
199
|
+
it "raises unauthorized if state is not :authorized" do
|
200
|
+
lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::Unauthorized, "Not authorized with RightScale")
|
201
|
+
end
|
202
|
+
|
203
|
+
it "returns true if authorized" do
|
204
|
+
@client.send(:state=, :authorized)
|
205
|
+
@client.send(:check_authorized).should be_true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context :renew_authorization do
|
210
|
+
it "renews authorization" do
|
211
|
+
@client.send(:renew_authorization).should be_true
|
212
|
+
end
|
213
|
+
|
214
|
+
it "waits to renew authorization" do
|
215
|
+
@client.send(:renew_authorization, 10).should be_true
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context :state= do
|
220
|
+
it "raises exception if state transition is invalid" do
|
221
|
+
lambda { @client.send(:state=, :expired) }.should raise_error(ArgumentError, "Invalid state transition: :pending -> :expired")
|
222
|
+
end
|
223
|
+
|
224
|
+
[:pending, :closed].each do |state|
|
225
|
+
context state do
|
226
|
+
it "stores new state" do
|
227
|
+
@client.send(:state=, state)
|
228
|
+
@client.state.should == state
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
[:authorized, :unauthorized, :expired, :failed].each do |state|
|
234
|
+
context state do
|
235
|
+
before(:each) do
|
236
|
+
@client.send(:state=, :authorized) if state == :expired
|
237
|
+
end
|
238
|
+
|
239
|
+
it "stores new state" do
|
240
|
+
@client.send(:state=, state)
|
241
|
+
@client.state.should == state
|
242
|
+
end
|
243
|
+
|
244
|
+
it "stores new state" do
|
245
|
+
@client.send(:state=, state).should == state
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when callbacks" do
|
249
|
+
it "makes callbacks with new state" do
|
250
|
+
callback_type = callback_state = nil
|
251
|
+
@client.status { |t, s| callback_type = t; callback_state = s }
|
252
|
+
@client.send(:state=, state)
|
253
|
+
callback_type.should == :auth
|
254
|
+
callback_state.should == state
|
255
|
+
end
|
256
|
+
|
257
|
+
it "log error if callback fails" do
|
258
|
+
@log.should_receive(:error).with("Failed status callback", StandardError).once
|
259
|
+
@client.status { |t, s| raise StandardError, "test" }
|
260
|
+
@client.send(:state=, state).should == state
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
it "does nothing if current state is the same" do
|
265
|
+
flexmock(@client.instance_variable_get(:@stats)["state"]).should_receive(:update).once
|
266
|
+
@client.send(:state=, state)
|
267
|
+
@client.send(:state=, state).should == state
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,576 @@
|
|
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', 'balanced_http_client'))
|
26
|
+
|
27
|
+
describe RightScale::BalancedHttpClient 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://my.com"
|
36
|
+
@urls = [@url]
|
37
|
+
@path = "/foo/bar"
|
38
|
+
@balancer = flexmock("balancer")
|
39
|
+
end
|
40
|
+
|
41
|
+
context :initialize do
|
42
|
+
['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY'].each do |proxy|
|
43
|
+
it "initializes use of proxy if #{proxy} defined in environment" do
|
44
|
+
ENV[proxy] = "https://my.proxy.com"
|
45
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer)
|
46
|
+
flexmock(RestClient).should_receive(:proxy=).with("https://my.proxy.com").once
|
47
|
+
RightScale::BalancedHttpClient.new(@urls)
|
48
|
+
ENV.delete(proxy)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "prepends scheme to proxy address if #{proxy} defined in environment" do
|
52
|
+
ENV[proxy] = "1.2.3.4"
|
53
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer)
|
54
|
+
flexmock(RestClient).should_receive(:proxy=).with("http://1.2.3.4").once
|
55
|
+
RightScale::BalancedHttpClient.new(@urls)
|
56
|
+
ENV.delete(proxy)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "creates request balancer with health checker" do
|
61
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).
|
62
|
+
with(@urls, hsh(:policy => RightSupport::Net::LB::HealthCheck)).and_return(@balancer).once
|
63
|
+
RightScale::BalancedHttpClient.new(@urls)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "accepts comma-separated list of URLs" do
|
67
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).
|
68
|
+
with(on { |arg| arg.should == ["url1", "url2"] }, Hash).and_return(@balancer).once
|
69
|
+
RightScale::BalancedHttpClient.new("url1, url2")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context :check_health do
|
74
|
+
before(:each) do
|
75
|
+
@urls = ["http://my0.com", @url]
|
76
|
+
@http_client = flexmock("http client")
|
77
|
+
flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
|
78
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "calls health check proc using first URL" do
|
82
|
+
@http_client.should_receive(:get).with("http://my0.com/health-check", Hash).once
|
83
|
+
@client.check_health
|
84
|
+
end
|
85
|
+
|
86
|
+
it "calls health check proc using specified URL" do
|
87
|
+
@http_client.should_receive(:get).with("http://my1.com/health-check", Hash).once
|
88
|
+
@client.check_health("http://my1.com")
|
89
|
+
end
|
90
|
+
|
91
|
+
RightScale::BalancedHttpClient::RETRY_STATUS_CODES.each do |code|
|
92
|
+
it "raises NotResponding for #{code}" do
|
93
|
+
@http_client.should_receive(:get).and_raise(RestExceptionMock.new(code))
|
94
|
+
lambda { @client.check_health("http://my1.com") }.should raise_error(RightScale::BalancedHttpClient::NotResponding)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context :request do
|
100
|
+
before(:each) do
|
101
|
+
@result = {:out => 123}
|
102
|
+
@json_result = JSON.dump(@result)
|
103
|
+
@json_decoded_result = {"out" => 123}
|
104
|
+
@response = flexmock("response")
|
105
|
+
@response.should_receive(:code).and_return(200).by_default
|
106
|
+
@response.should_receive(:body).and_return(@json_result).by_default
|
107
|
+
@response.should_receive(:headers).and_return({:status => "200 OK"}).by_default
|
108
|
+
flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return("random uuid").by_default
|
109
|
+
@balancer.should_receive(:request).and_yield(@url).by_default
|
110
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer).by_default
|
111
|
+
@http_client = flexmock("http client")
|
112
|
+
flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
|
113
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with no options" do
|
117
|
+
before(:each) do
|
118
|
+
@options = {}
|
119
|
+
@http_client.should_receive(:get).with("#{@url}#{@path}", on { |a| @options = a }).and_return(nil)
|
120
|
+
@client.send(:request, :get, @path).should be_nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it "sets default open and request timeouts" do
|
124
|
+
@options[:open_timeout].should == RightScale::BalancedHttpClient::DEFAULT_OPEN_TIMEOUT
|
125
|
+
@options[:timeout].should == RightScale::BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT
|
126
|
+
end
|
127
|
+
|
128
|
+
it "sets random request uuid in header" do
|
129
|
+
@options[:headers]["X-Request-Lineage-Uuid"] == "random uuid"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "sets response to be JSON-encoded" do
|
133
|
+
@options[:headers][:accept] == "application/json"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "does not set API version" do
|
137
|
+
@options[:headers]["X-API-Version"].should be_nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "with options" do
|
142
|
+
before(:each) do
|
143
|
+
@options = {}
|
144
|
+
create_options = {
|
145
|
+
:request_uuid => "my uuid",
|
146
|
+
:api_version => "1.0" }
|
147
|
+
request_options = {
|
148
|
+
:open_timeout => 1,
|
149
|
+
:request_timeout => 2,
|
150
|
+
:headers => {"Authorization" => "Bearer <session>"} }
|
151
|
+
@client = RightScale::BalancedHttpClient.new(@urls, create_options)
|
152
|
+
@http_client.should_receive(:get).with("#{@url}#{@path}", on { |a| @options = a }).and_return(nil)
|
153
|
+
@client.send(:request, :get, @path, nil, request_options).should be_nil
|
154
|
+
end
|
155
|
+
|
156
|
+
it "sets open and request timeouts" do
|
157
|
+
@options[:open_timeout].should == 1
|
158
|
+
@options[:timeout].should == 2
|
159
|
+
end
|
160
|
+
|
161
|
+
it "sets request uuid in header" do
|
162
|
+
@options[:headers]["X-Request-Lineage-Uuid"] == "my uuid"
|
163
|
+
end
|
164
|
+
|
165
|
+
it "sets API version in header" do
|
166
|
+
@options[:headers]["X-API-Version"].should == "1.0"
|
167
|
+
end
|
168
|
+
|
169
|
+
it "uses headers for setting authorization in header" do
|
170
|
+
@options[:headers]["Authorization"].should == "Bearer <session>"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
[:get, :delete].each do |verb|
|
175
|
+
context "with #{verb.inspect}" do
|
176
|
+
it "uses form-encoded query option for parameters" do
|
177
|
+
params = {:id => 10}
|
178
|
+
@http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:query] == params &&
|
179
|
+
a[:payload].nil? }).and_return(nil)
|
180
|
+
@client.send(:request, verb, @path, params).should be_nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
[:post, :put].each do |verb|
|
186
|
+
context "with #{verb.inspect}" do
|
187
|
+
it "uses JSON-encoded payload options for parameters" do
|
188
|
+
payload = {:pay => "load"}
|
189
|
+
json_payload = JSON.dump(payload)
|
190
|
+
@http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:payload] == json_payload &&
|
191
|
+
a[:query].nil? }).and_return(nil).once
|
192
|
+
@client.send(:request, verb, @path, payload).should be_nil
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "health check proc" do
|
198
|
+
it "removes user and password from URL when checking health" do
|
199
|
+
@url = "http://me:pass@my.com"
|
200
|
+
@client = RightScale::BalancedHttpClient.new(@url, :health_check_path => "/health-check")
|
201
|
+
@http_client.should_receive(:get).with("http://my.com/health-check", Hash).once
|
202
|
+
@client.instance_variable_get(:@health_check_proc).call(@url)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "uses default path if none specified" do
|
206
|
+
@client = RightScale::BalancedHttpClient.new(@url)
|
207
|
+
@http_client.should_receive(:get).with("http://my.com/health-check", Hash).once
|
208
|
+
@client.instance_variable_get(:@health_check_proc).call(@url)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "appends health check path to any existing path" do
|
212
|
+
@url = "http://my.com/foo"
|
213
|
+
@client = RightScale::BalancedHttpClient.new(@url)
|
214
|
+
@http_client.should_receive(:get).with("http://my.com/foo/health-check", Hash).once
|
215
|
+
@client.instance_variable_get(:@health_check_proc).call(@url)
|
216
|
+
end
|
217
|
+
|
218
|
+
it "uses fixed timeout values" do
|
219
|
+
@client = RightScale::BalancedHttpClient.new(@url, :open_timeout => 5, :request_timeout => 30)
|
220
|
+
@http_client.should_receive(:get).with(String, {:open_timeout => 2, :timeout => 5}).once
|
221
|
+
@client.instance_variable_get(:@health_check_proc).call(@url)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "sets API version if specified" do
|
225
|
+
@client = RightScale::BalancedHttpClient.new(@url, :api_version => "2.0")
|
226
|
+
@http_client.should_receive(:get).with(String, hsh(:headers => {"X-API-Version" => "2.0"})).once
|
227
|
+
@client.instance_variable_get(:@health_check_proc).call(@url)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
it "sends request using HTTPClient via balancer" do
|
232
|
+
@http_client.should_receive(:post).with("#{@url}#{@path}", Hash).and_return(nil).once
|
233
|
+
@balancer.should_receive(:request).and_yield(@url).once
|
234
|
+
@client.send(:request, :post, @path).should be_nil
|
235
|
+
end
|
236
|
+
|
237
|
+
it "returns location header for 201 response" do
|
238
|
+
@response.should_receive(:code).and_return(201).once
|
239
|
+
@response.should_receive(:body).and_return("").once
|
240
|
+
@response.should_receive(:headers).and_return({:status => "201 Created", :location => "/href"}).once
|
241
|
+
@balancer.should_receive(:request).and_return(@response).once
|
242
|
+
@client.send(:request, :get, @path).should == "/href"
|
243
|
+
end
|
244
|
+
|
245
|
+
it "returns JSON decoded response" do
|
246
|
+
@response.should_receive(:body).and_return(@json_result).once
|
247
|
+
@balancer.should_receive(:request).and_return(@response).once
|
248
|
+
@client.send(:request, :get, @path).should == @json_decoded_result
|
249
|
+
end
|
250
|
+
|
251
|
+
it "returns nil if response is empty" do
|
252
|
+
@response.should_receive(:body).and_return("").once
|
253
|
+
@balancer.should_receive(:request).and_return(@response).once
|
254
|
+
@client.send(:request, :get, @path).should be_nil
|
255
|
+
end
|
256
|
+
|
257
|
+
it "returns nil if response is nil" do
|
258
|
+
@balancer.should_receive(:request).and_return(nil).once
|
259
|
+
@client.send(:request, :get, @path).should be_nil
|
260
|
+
end
|
261
|
+
|
262
|
+
it "returns nil if response status indicates no content" do
|
263
|
+
@response.should_receive(:code).and_return(204).once
|
264
|
+
@balancer.should_receive(:request).and_return(@response).once
|
265
|
+
@client.send(:request, :get, @path).should be_nil
|
266
|
+
end
|
267
|
+
|
268
|
+
it "handles NoResult response from balancer" do
|
269
|
+
no_result = RightSupport::Net::NoResult.new("no result", {})
|
270
|
+
@log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
|
271
|
+
@balancer.should_receive(:request).and_yield(@url).once
|
272
|
+
flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_raise(no_result).once
|
273
|
+
flexmock(@client).should_receive(:handle_no_result).with(RightSupport::Net::NoResult, @url, Proc).and_yield(no_result).once
|
274
|
+
@client.send(:request, :get, @path)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "reports and re-raises unexpected exception" do
|
278
|
+
@log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
|
279
|
+
@balancer.should_receive(:request).and_raise(RuntimeError).once
|
280
|
+
lambda { @client.send(:request, :get, @path) }.should raise_error(RuntimeError)
|
281
|
+
end
|
282
|
+
|
283
|
+
context "when logging" do
|
284
|
+
before(:each) do
|
285
|
+
now = Time.now
|
286
|
+
flexmock(Time).should_receive(:now).and_return(now, now + 0.01)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "logs request and response" do
|
290
|
+
@http_client.should_receive(:post).and_return(@response)
|
291
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
292
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
293
|
+
@client.send(:request, :post, @path)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "logs using specified log level" do
|
297
|
+
@http_client.should_receive(:post).and_return(@response)
|
298
|
+
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar").once
|
299
|
+
@log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
300
|
+
@client.send(:request, :post, @path, {}, :log_level => :debug)
|
301
|
+
end
|
302
|
+
|
303
|
+
it "logs response length using header :content_length if available" do
|
304
|
+
@response.should_receive(:headers).and_return({:status => "200 OK", :content_length => 99})
|
305
|
+
@http_client.should_receive(:post).and_return(@response)
|
306
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
307
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 99 bytes").once
|
308
|
+
@client.send(:request, :post, @path)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "omits user and password from logged host" do
|
312
|
+
url = "http://111:secret@my.com"
|
313
|
+
@client = RightScale::BalancedHttpClient.new([url])
|
314
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
315
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
316
|
+
@http_client.should_receive(:post).with("#{url}#{@path}", Hash).and_return(@response).once
|
317
|
+
@balancer.should_receive(:request).and_yield(url).once
|
318
|
+
@client.send(:request, :post, @path)
|
319
|
+
end
|
320
|
+
|
321
|
+
context "when filtering" do
|
322
|
+
before(:each) do
|
323
|
+
@client = RightScale::BalancedHttpClient.new(@urls, :filter_params => [:secret])
|
324
|
+
@params = {:public => "data", "secret" => "data", "very secret" => "data"}
|
325
|
+
@options = {:filter_params => ["very secret"]}
|
326
|
+
@filtered_params = "{:public=>\"data\", \"secret\"=>\"<hidden>\", \"very secret\"=>\"<hidden>\"}"
|
327
|
+
end
|
328
|
+
|
329
|
+
it "logs request with filtered params if in debug mode" do
|
330
|
+
@log.should_receive(:level).and_return(:debug)
|
331
|
+
@http_client.should_receive(:post).and_return(@response)
|
332
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar #{@filtered_params}").once
|
333
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
334
|
+
@client.send(:request, :post, @path, @params, @options)
|
335
|
+
end
|
336
|
+
|
337
|
+
it "logs response failure including filtered params" do
|
338
|
+
@http_client.should_receive(:post).and_raise(RestExceptionMock.new(400, "bad data")).once
|
339
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
340
|
+
@log.should_receive(:error).with("Failed <random uuid> in 10ms | 400 [http://my.com/foo/bar #{@filtered_params}] | 400 Bad Request: bad data").once
|
341
|
+
lambda { @client.send(:request, :post, @path, @params, @options) }.should raise_error(RuntimeError)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
context :handle_no_result do
|
348
|
+
before(:each) do
|
349
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {})
|
350
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
351
|
+
@yielded = nil
|
352
|
+
@proc = lambda { |e| @yielded = e }
|
353
|
+
end
|
354
|
+
|
355
|
+
it "uses configured server name" do
|
356
|
+
@client = RightScale::BalancedHttpClient.new(@urls, :server_name => "Some server")
|
357
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
358
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "Some server not responding")
|
359
|
+
end
|
360
|
+
|
361
|
+
it "defaults server name to host name" do
|
362
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
363
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
364
|
+
end
|
365
|
+
|
366
|
+
it "uses last exception stored in NoResult details" do
|
367
|
+
gateway_timeout = RestExceptionMock.new(504, "server timeout")
|
368
|
+
bad_request = RestExceptionMock.new(400, "bad data")
|
369
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout, @url => bad_request})
|
370
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
|
371
|
+
@yielded.should == bad_request
|
372
|
+
end
|
373
|
+
|
374
|
+
it "uses server name in NotResponding exception if there are no details" do
|
375
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
376
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
377
|
+
@yielded.should == @no_result
|
378
|
+
end
|
379
|
+
|
380
|
+
it "uses http_body in raised NotResponding exception if status code is 504" do
|
381
|
+
gateway_timeout = RestExceptionMock.new(504, "server timeout")
|
382
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
|
383
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
384
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "server timeout")
|
385
|
+
@yielded.should == gateway_timeout
|
386
|
+
end
|
387
|
+
|
388
|
+
it "uses raise NotResponding if status code is 504 and http_body is nil or empty" do
|
389
|
+
gateway_timeout = RestExceptionMock.new(504, "")
|
390
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
|
391
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
392
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
393
|
+
@yielded.should == gateway_timeout
|
394
|
+
end
|
395
|
+
|
396
|
+
[502, 503].each do |code|
|
397
|
+
it "uses server name in NotResponding exception if status code is #{code}" do
|
398
|
+
e = RestExceptionMock.new(code)
|
399
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {@url => e})
|
400
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
401
|
+
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
it "raises last exception in details if not retryable" do
|
406
|
+
bad_request = RestExceptionMock.new(400, "bad data")
|
407
|
+
@no_result = RightSupport::Net::NoResult.new("no result", {@url => bad_request})
|
408
|
+
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
|
409
|
+
@yielded.should == bad_request
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
context :report_failure do
|
414
|
+
before(:each) do
|
415
|
+
@started_at = Time.now
|
416
|
+
flexmock(Time).should_receive(:now).and_return(@started_at + 0.01)
|
417
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
418
|
+
end
|
419
|
+
|
420
|
+
it "logs exception" do
|
421
|
+
exception = RestExceptionMock.new(400, "bad data")
|
422
|
+
@log.should_receive(:error).with("Failed <uuid> in 10ms | 400 [http://my.com/foo/bar \"params\"] | 400 Bad Request: bad data").once
|
423
|
+
@client.send(:report_failure, @url, @path, "params", [], "uuid", @started_at, exception)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "logs error string" do
|
427
|
+
@log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar \"params\"] | bad data").once
|
428
|
+
@client.send(:report_failure, @url, @path, "params", [], "uuid", @started_at, "bad data")
|
429
|
+
end
|
430
|
+
|
431
|
+
it "filters params" do
|
432
|
+
@log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar {:secret=>\"<hidden>\"}] | bad data").once
|
433
|
+
@client.send(:report_failure, @url, @path, {:secret => "data"}, ["secret"], "uuid", @started_at, "bad data")
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
context :log_text do
|
438
|
+
before(:each) do
|
439
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
440
|
+
end
|
441
|
+
|
442
|
+
context "when no exception" do
|
443
|
+
|
444
|
+
context "in info mode with no host" do
|
445
|
+
it "generates text containing path" do
|
446
|
+
text = @client.send(:log_text, @path, {:value => 123}, [])
|
447
|
+
text.should == "/foo/bar"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
context "in info mode with host" do
|
452
|
+
it "generates text containing host and path" do
|
453
|
+
text = @client.send(:log_text, @path, {:value => 123}, [], @url)
|
454
|
+
text.should == "[http://my.com/foo/bar]"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "and in debug mode" do
|
459
|
+
it "generates text containing containing host, path, and filtered params" do
|
460
|
+
@log.should_receive(:level).and_return(:debug)
|
461
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url)
|
462
|
+
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}]"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
context "when exception" do
|
468
|
+
it "includes params regardless of mode" do
|
469
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, "failed")
|
470
|
+
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | failed"
|
471
|
+
end
|
472
|
+
|
473
|
+
it "includes exception text" do
|
474
|
+
exception = RestExceptionMock.new(400, "bad data")
|
475
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, exception)
|
476
|
+
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | 400 Bad Request: bad data"
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
context :filter do
|
482
|
+
before(:each) do
|
483
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
484
|
+
end
|
485
|
+
|
486
|
+
it "applies filters" do
|
487
|
+
filter = ["secret"]
|
488
|
+
params = {:some => 1, "secret" => "data"}
|
489
|
+
@client.send(:filter, params, filter).should == {:some => 1, "secret" => "<hidden>"}
|
490
|
+
end
|
491
|
+
|
492
|
+
it "converts param names to string before comparing to filter list" do
|
493
|
+
filter = ["secret", "very secret"]
|
494
|
+
params = {:some => 1, :secret => "data", "very secret" => "data"}
|
495
|
+
@client.send(:filter, params, filter).should == {:some => 1, :secret => "<hidden>", "very secret" => "<hidden>"}
|
496
|
+
end
|
497
|
+
|
498
|
+
it "does not filter if no filters are specified" do
|
499
|
+
params = {:some => 1, "secret" => "data"}
|
500
|
+
@client.send(:filter, params, []).should == params
|
501
|
+
end
|
502
|
+
|
503
|
+
it "does not filter if params is not a hash" do
|
504
|
+
@client.send(:filter, "params", ["secret"]).should == "params"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context :split do
|
509
|
+
before(:each) do
|
510
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
511
|
+
end
|
512
|
+
|
513
|
+
it "leaves array as an array" do
|
514
|
+
@client.send(:split, ["data"]).should == ["data"]
|
515
|
+
end
|
516
|
+
|
517
|
+
it "turns nil into an empty array" do
|
518
|
+
@client.send(:split, nil).should == []
|
519
|
+
end
|
520
|
+
|
521
|
+
it "splits a string using default pattern" do
|
522
|
+
@client.send(:split, "some,data").should == ["some", "data"]
|
523
|
+
end
|
524
|
+
|
525
|
+
it "splits a string using specified pattern" do
|
526
|
+
@client.send(:split, "some#data", /#/).should == ["some", "data"]
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
context :exception_text do
|
531
|
+
context "when string exception" do
|
532
|
+
it "adds exception text" do
|
533
|
+
RightScale::BalancedHttpClient.exception_text("failed").should == "failed"
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
context "when REST exception" do
|
538
|
+
it "adds exception code/type and any http_body" do
|
539
|
+
exception = RestExceptionMock.new(400, "bad data")
|
540
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request: bad data"
|
541
|
+
end
|
542
|
+
|
543
|
+
it "adds exception code/type but omits http_body if it is html" do
|
544
|
+
exception = RestExceptionMock.new(400, "<html> bad </html>")
|
545
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request"
|
546
|
+
end
|
547
|
+
|
548
|
+
it "adds exception code/type and omits http_body if it is blank" do
|
549
|
+
exception = RestExceptionMock.new(400)
|
550
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request"
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
context "when NoResult exception" do
|
555
|
+
it "adds exception class and message" do
|
556
|
+
exception = RightSupport::Net::NoResult.new("no result")
|
557
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "RightSupport::Net::NoResult: no result"
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
context "when non-REST, non-NoResult exception" do
|
562
|
+
it "adds exception class and message" do
|
563
|
+
exception = ArgumentError.new("bad arg")
|
564
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "ArgumentError: bad arg"
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
context "when non-REST, non-NoResult exception with backtrace" do
|
569
|
+
it "adds exception class, message, and backtrace" do
|
570
|
+
exception = ArgumentError.new("bad arg")
|
571
|
+
flexmock(exception).should_receive(:backtrace).and_return(["line 1", "line 2"])
|
572
|
+
RightScale::BalancedHttpClient.exception_text(exception).should == "ArgumentError: bad arg in\nline 1\nline 2"
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|